diff --git a/A.Global_Example.php b/A.Global_Example.php new file mode 100644 index 0000000..1b0827e --- /dev/null +++ b/A.Global_Example.php @@ -0,0 +1,182 @@ + + + + + <?php echo TEST_NAME; ?> + + + + + +
+ Condorcet Class +
+ +

+ + Condorcet Class version :
+ + + Number of Candidates : + countCandidates(); ?> + | + Number of votes : + countVotes(); ?> + + +

Candidates list :

+ + + + +

Registered votes details :

+getVotesList() as $vote) { + echo '
'; + + echo ''.implode(' / ', $vote->getTags()).'
'; + + echo '
    '; + + foreach ($vote as $rank => $value) { + if ($rank === 'tag') { + continue; + } ?> + +
  1. + +
'; + } +?> + +
+ +

Winner by natural Condorcet :

+ + + getWinner() !== null) { + echo $election->getWinner(); + } else { + echo 'The votes of this group do not allow natural Condorcet winner because of Condorcet paradox.'; + } + ?> +
+ computed in getLastTimer(), 5); ?> second(s).
+ +

Loser by natural Condorcet :

+ + + getLoser() !== null) { + echo $election->getLoser(); + } else { + echo 'The votes of this group do not allow natural Condorcet loser because of Condorcet paradox.'; + } + ?> +
+ computed in getLastTimer(), 5); ?> second(s).
+ + +


+ + + +

Ranking by :

+ + getResult($method); + $lastTimer = $election->getLastTimer(); + + if ($method === 'Kemeny–Young' && !empty($result->getWarning(\CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung::CONFLICT_WARNING_CODE))) { + $kemeny_conflicts = explode(';', $result->getWarning(\CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung::CONFLICT_WARNING_CODE)[0]['msg']); + + echo 'Arbitrary results: Kemeny-Young has '.$kemeny_conflicts[0].' possible solutions at score '.$kemeny_conflicts[1].''; + } + ?> + +
+		
+		
+ + computed in second(s). + + +



+Total computed in getGlobalTimer(), 5); ?> second(s). +
+getTimerManager()->getHistory()); ?> +


+ +

Computing statistics :

+ +

Pairwise :

+ +
+	getPairwise())); ?>
+	
+ + +

Stats for :

+ +
+		getResult($method)->getStats())); ?>
+		
+ + + +


+ +

Debug Data :

+ +

Defaut method (not used explicitly before) :

+ +
+
+ 
+ + + + + diff --git a/include/Condorcet/Assets/Logos/condorcet-logo.png b/include/Condorcet/Assets/Logos/condorcet-logo.png new file mode 100644 index 0000000..94c306d Binary files /dev/null and b/include/Condorcet/Assets/Logos/condorcet-logo.png differ diff --git a/include/Condorcet/Assets/Logos/condorcet-logo.psd b/include/Condorcet/Assets/Logos/condorcet-logo.psd new file mode 100644 index 0000000..f69b111 Binary files /dev/null and b/include/Condorcet/Assets/Logos/condorcet-logo.psd differ diff --git a/include/Condorcet/Benchmarks/AddVotesBench.php b/include/Condorcet/Benchmarks/AddVotesBench.php new file mode 100644 index 0000000..66bbdfe --- /dev/null +++ b/include/Condorcet/Benchmarks/AddVotesBench.php @@ -0,0 +1,37 @@ +election = $election = new Election; + + $candidates = []; + + for ($i=0; $i < 100; $i++) { + $candidates[] = $election->addCandidate(); + } + + for ($i = 0; $i < 1_000; $i++) { + $oneVote = $randomizer->shuffleArray($candidates); + $election->addVote($oneVote); + } + } +} diff --git a/include/Condorcet/Benchmarks/History/AddVotesBench.md b/include/Condorcet/Benchmarks/History/AddVotesBench.md new file mode 100644 index 0000000..0118090 --- /dev/null +++ b/include/Condorcet/Benchmarks/History/AddVotesBench.md @@ -0,0 +1,45 @@ +# v4.0-beta1 Branch + +* AMD Ryzen 9 5900X + +PHPBench (1.2.5) +with PHP version 8.1.6, xdebug ❌, opcache ✔ (with JIT Tracing) + +```php + #[Bench\Warmup(1)] + #[Bench\Iterations(3)] + #[Bench\Revs(1)] +``` + +# v4.0-beta1 Branch + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++------+---------------+------------------------------+-----+------+-------------+----------+--------------+----------------+ +| iter | benchmark | subject | set | revs | mem_peak | time_avg | comp_z_value | comp_deviation | ++------+---------------+------------------------------+-----+------+-------------+----------+--------------+----------------+ +| 0 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 59,269,648b | 0.714s | -1.14σ | -0.10% | +| 1 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 58,871,856b | 0.715s | -0.16σ | -0.01% | +| 2 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 58,871,856b | 0.716s | +1.30σ | +0.12% | ++------+---------------+------------------------------+-----+------+-------------+----------+--------------+----------------+ + + +# v3.3.3 + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++------+---------------+------------------------------+-----+------+-------------+----------+--------------+----------------+ +| iter | benchmark | subject | set | revs | mem_peak | time_avg | comp_z_value | comp_deviation | ++------+---------------+------------------------------+-----+------+-------------+----------+--------------+----------------+ +| 0 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 59,029,584b | 18.906s | -1.00σ | -0.69% | +| 1 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 59,029,504b | 18.990s | -0.36σ | -0.25% | +| 2 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 59,030,016b | 19.218s | +1.36σ | +0.94% | ++------+---------------+------------------------------+-----+------+-------------+----------+--------------+----------------+ + +# v3.2 + ++------+---------------+------------------------------+-----+------+--------------+----------+--------------+----------------+ +| iter | benchmark | subject | set | revs | mem_peak | time_avg | comp_z_value | comp_deviation | ++------+---------------+------------------------------+-----+------+--------------+----------+--------------+----------------+ +| 0 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 117,355,360b | 18.869s | +0.96σ | +0.84% | +| 1 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 117,023,280b | 18.488s | -1.38σ | -1.20% | +| 2 | AddVotesBench | benchVotesWithManyCandidates | | 1 | 117,023,280b | 18.780s | +0.42σ | +0.36% | ++------+---------------+------------------------------+-----+------+--------------+----------+--------------+----------------+ \ No newline at end of file diff --git a/include/Condorcet/Benchmarks/History/MethodsBench.md b/include/Condorcet/Benchmarks/History/MethodsBench.md new file mode 100644 index 0000000..982c82b --- /dev/null +++ b/include/Condorcet/Benchmarks/History/MethodsBench.md @@ -0,0 +1,1539 @@ +# v4.1 Branch + +1000 random votes different for each test, variable number of candidates (look at column "set") + +* AMD Ryzen 9 5900X + +PHPBench (1.2.5) +with PHP version 8.1.7, xdebug ❌, opcache ✔ (with JIT Tracing) + +```php + #[Bench\Warmup(1)] + #[Bench\Iterations(3)] + #[Bench\Revs(1)] +``` + +### Non-Proportional +_Same as 4.0_ + +### Proportional + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++--------------------------+-------------------+-----------------------+------+-----+-----------+-------------+---------+ +| benchmark | subject | set | revs | its | mem_peak | mode | rstdev | ++--------------------------+-------------------+-----------------------+------+-----+-----------+-------------+---------+ +| MethodsProportionalBench | benchByCandidates | STV,3 | 1 | 3 | 6.140mb | 0.002796s | ±18.88% | +| MethodsProportionalBench | benchByCandidates | CPO STV,3 | 1 | 3 | 6.150mb | 0.013553s | ±46.44% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,3 | 1 | 3 | 3.963mb | 0.001552s | ±7.83% | +| MethodsProportionalBench | benchByCandidates | Jefferson,3 | 1 | 3 | 3.963mb | 0.001470s | ±2.80% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,3 | 1 | 3 | 3.963mb | 0.001400s | ±6.81% | +| MethodsProportionalBench | benchByCandidates | STV,4 | 1 | 3 | 6.649mb | 0.003776s | ±5.99% | +| MethodsProportionalBench | benchByCandidates | CPO STV,4 | 1 | 3 | 6.770mb | 0.020136s | ±28.89% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,4 | 1 | 3 | 4.407mb | 0.001897s | ±2.65% | +| MethodsProportionalBench | benchByCandidates | Jefferson,4 | 1 | 3 | 4.407mb | 0.001770s | ±5.52% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,4 | 1 | 3 | 4.407mb | 0.001668s | ±11.97% | +| MethodsProportionalBench | benchByCandidates | STV,5 | 1 | 3 | 7.470mb | 0.004705s | ±1.49% | +| MethodsProportionalBench | benchByCandidates | CPO STV,5 | 1 | 3 | 7.669mb | 0.023462s | ±15.21% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,5 | 1 | 3 | 4.851mb | 0.002089s | ±8.17% | +| MethodsProportionalBench | benchByCandidates | Jefferson,5 | 1 | 3 | 4.851mb | 0.001867s | ±2.44% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,5 | 1 | 3 | 4.851mb | 0.001759s | ±7.13% | +| MethodsProportionalBench | benchByCandidates | STV,6 | 1 | 3 | 8.292mb | 0.005555s | ±8.11% | +| MethodsProportionalBench | benchByCandidates | CPO STV,6 | 1 | 3 | 10.407mb | 0.169012s | ±2.73% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,6 | 1 | 3 | 5.295mb | 0.002087s | ±0.79% | +| MethodsProportionalBench | benchByCandidates | Jefferson,6 | 1 | 3 | 5.295mb | 0.001869s | ±0.41% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,6 | 1 | 3 | 5.295mb | 0.001886s | ±1.77% | +| MethodsProportionalBench | benchByCandidates | STV,7 | 1 | 3 | 9.113mb | 0.008930s | ±7.93% | +| MethodsProportionalBench | benchByCandidates | CPO STV,7 | 1 | 3 | 13.333mb | 0.344687s | ±0.74% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,7 | 1 | 3 | 5.739mb | 0.002214s | ±4.71% | +| MethodsProportionalBench | benchByCandidates | Jefferson,7 | 1 | 3 | 5.739mb | 0.002079s | ±1.59% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,7 | 1 | 3 | 5.739mb | 0.002031s | ±8.01% | +| MethodsProportionalBench | benchByCandidates | STV,8 | 1 | 3 | 10.609mb | 0.009126s | ±0.97% | +| MethodsProportionalBench | benchByCandidates | CPO STV,8 | 1 | 3 | 18.034mb | 0.641492s | ±0.78% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,8 | 1 | 3 | 6.503mb | 0.002300s | ±22.88% | +| MethodsProportionalBench | benchByCandidates | Jefferson,8 | 1 | 3 | 6.503mb | 0.002214s | ±3.09% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,8 | 1 | 3 | 6.503mb | 0.002173s | ±2.41% | +| MethodsProportionalBench | benchByCandidates | STV,9 | 1 | 3 | 11.447mb | 0.007795s | ±6.70% | +| MethodsProportionalBench | benchByCandidates | CPO STV,9 | 1 | 3 | 83.423mb | 5.907340s | ±0.54% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,9 | 1 | 3 | 6.961mb | 0.002415s | ±2.68% | +| MethodsProportionalBench | benchByCandidates | Jefferson,9 | 1 | 3 | 6.961mb | 0.002306s | ±4.59% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,9 | 1 | 3 | 6.961mb | 0.002176s | ±2.10% | +| MethodsProportionalBench | benchByCandidates | STV,10 | 1 | 3 | 12.233mb | 0.008335s | ±12.54% | +| MethodsProportionalBench | benchByCandidates | CPO STV,10 | 1 | 3 | 155.669mb | 13.365452s | ±1.92% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,10 | 1 | 3 | 7.408mb | 0.004098s | ±15.02% | +| MethodsProportionalBench | benchByCandidates | Jefferson,10 | 1 | 3 | 7.408mb | 0.003836s | ±19.18% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,10 | 1 | 3 | 7.408mb | 0.003827s | ±17.49% | +| MethodsProportionalBench | benchByCandidates | STV,11 | 1 | 3 | 13.091mb | 0.010444s | ±6.50% | +| MethodsProportionalBench | benchByCandidates | CPO STV,11 | 1 | 3 | 286.707mb | 25.839933s | ±1.15% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,11 | 1 | 3 | 7.854mb | 0.002936s | ±1.94% | +| MethodsProportionalBench | benchByCandidates | Jefferson,11 | 1 | 3 | 7.854mb | 0.002715s | ±4.13% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,11 | 1 | 3 | 7.854mb | 0.002782s | ±0.63% | +| MethodsProportionalBench | benchByCandidates | STV,12 | 1 | 3 | 13.879mb | 0.010493s | ±1.24% | +| MethodsProportionalBench | benchByCandidates | CPO STV,12 | 1 | 3 | 2.417gb | 310.681427s | ±0.34% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,12 | 1 | 3 | 8.300mb | 0.003592s | ±51.21% | +| MethodsProportionalBench | benchByCandidates | Jefferson,12 | 1 | 3 | 8.299mb | 0.003187s | ±34.53% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,12 | 1 | 3 | 8.300mb | 0.003387s | ±32.42% | +| MethodsProportionalBench | benchByCandidates | STV,13 | 1 | 3 | 14.701mb | 0.012803s | ±1.34% | +| MethodsProportionalBench | benchByCandidates | CPO STV,13 | 1 | 3 | 14.732mb | 0.004578s | ±0.85% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,13 | 1 | 3 | 8.745mb | 0.003384s | ±4.92% | +| MethodsProportionalBench | benchByCandidates | Jefferson,13 | 1 | 3 | 8.745mb | 0.003551s | ±4.73% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,13 | 1 | 3 | 8.745mb | 0.003303s | ±4.17% | +| MethodsProportionalBench | benchByCandidates | STV,14 | 1 | 3 | 15.522mb | 0.013331s | ±4.09% | +| MethodsProportionalBench | benchByCandidates | CPO STV,14 | 1 | 3 | 15.487mb | 0.005143s | ±14.17% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,14 | 1 | 3 | 9.191mb | 0.003328s | ±0.64% | +| MethodsProportionalBench | benchByCandidates | Jefferson,14 | 1 | 3 | 9.191mb | 0.003417s | ±3.84% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,14 | 1 | 3 | 9.191mb | 0.003650s | ±14.27% | +| MethodsProportionalBench | benchByCandidates | STV,15 | 1 | 3 | 16.346mb | 0.014077s | ±4.13% | +| MethodsProportionalBench | benchByCandidates | CPO STV,15 | 1 | 3 | 16.309mb | 0.004995s | ±4.99% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,15 | 1 | 3 | 9.640mb | 0.005224s | ±31.24% | +| MethodsProportionalBench | benchByCandidates | Jefferson,15 | 1 | 3 | 9.639mb | 0.003729s | ±16.58% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,15 | 1 | 3 | 9.640mb | 0.003584s | ±0.64% | +| MethodsProportionalBench | benchByCandidates | STV,16 | 1 | 3 | 18.446mb | 0.015429s | ±8.53% | +| MethodsProportionalBench | benchByCandidates | CPO STV,16 | 1 | 3 | 18.411mb | 0.004596s | ±7.59% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,16 | 1 | 3 | 10.728mb | 0.004133s | ±0.77% | +| MethodsProportionalBench | benchByCandidates | Jefferson,16 | 1 | 3 | 10.727mb | 0.003669s | ±4.98% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,16 | 1 | 3 | 10.728mb | 0.004017s | ±4.55% | +| MethodsProportionalBench | benchByCandidates | STV,17 | 1 | 3 | 19.326mb | 0.016124s | ±2.04% | +| MethodsProportionalBench | benchByCandidates | CPO STV,17 | 1 | 3 | 19.287mb | 0.005705s | ±1.88% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,17 | 1 | 3 | 11.237mb | 0.004140s | ±1.00% | +| MethodsProportionalBench | benchByCandidates | Jefferson,17 | 1 | 3 | 11.236mb | 0.003962s | ±4.62% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,17 | 1 | 3 | 11.237mb | 0.003954s | ±4.42% | +| MethodsProportionalBench | benchByCandidates | STV,18 | 1 | 3 | 20.157mb | 0.016955s | ±4.44% | +| MethodsProportionalBench | benchByCandidates | CPO STV,18 | 1 | 3 | 20.115mb | 0.005920s | ±1.38% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,18 | 1 | 3 | 11.700mb | 0.004243s | ±11.95% | +| MethodsProportionalBench | benchByCandidates | Jefferson,18 | 1 | 3 | 11.699mb | 0.004167s | ±5.96% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,18 | 1 | 3 | 11.700mb | 0.004331s | ±10.30% | +| MethodsProportionalBench | benchByCandidates | STV,19 | 1 | 3 | 20.984mb | 0.017616s | ±1.99% | +| MethodsProportionalBench | benchByCandidates | CPO STV,19 | 1 | 3 | 20.940mb | 0.005932s | ±7.57% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,19 | 1 | 3 | 12.151mb | 0.004609s | ±3.18% | +| MethodsProportionalBench | benchByCandidates | Jefferson,19 | 1 | 3 | 12.151mb | 0.004244s | ±4.38% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,19 | 1 | 3 | 12.151mb | 0.004391s | ±3.48% | +| MethodsProportionalBench | benchByCandidates | STV,20 | 1 | 3 | 21.808mb | 0.019353s | ±6.03% | +| MethodsProportionalBench | benchByCandidates | CPO STV,20 | 1 | 3 | 21.765mb | 0.006336s | ±0.70% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,20 | 1 | 3 | 12.602mb | 0.004655s | ±2.31% | +| MethodsProportionalBench | benchByCandidates | Jefferson,20 | 1 | 3 | 12.602mb | 0.004561s | ±0.98% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,20 | 1 | 3 | 12.602mb | 0.004507s | ±0.33% | +| MethodsProportionalBench | benchByCandidates | STV,30 | 1 | 3 | 30.114mb | 0.027635s | ±2.53% | +| MethodsProportionalBench | benchByCandidates | CPO STV,30 | 1 | 3 | 30.138mb | 0.010538s | ±12.38% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,30 | 1 | 3 | 17.213mb | 0.008364s | ±9.75% | +| MethodsProportionalBench | benchByCandidates | Jefferson,30 | 1 | 3 | 17.212mb | 0.006590s | ±3.75% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,30 | 1 | 3 | 17.213mb | 0.006709s | ±1.63% | +| MethodsProportionalBench | benchByCandidates | STV,40 | 1 | 3 | 41.236mb | 0.037545s | ±4.36% | +| MethodsProportionalBench | benchByCandidates | CPO STV,40 | 1 | 3 | 41.193mb | 0.012487s | ±2.01% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,40 | 1 | 3 | 23.370mb | 0.009508s | ±11.16% | +| MethodsProportionalBench | benchByCandidates | Jefferson,40 | 1 | 3 | 23.369mb | 0.009279s | ±1.30% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,40 | 1 | 3 | 23.370mb | 0.009423s | ±4.00% | +| MethodsProportionalBench | benchByCandidates | STV,50 | 1 | 3 | 49.602mb | 0.047640s | ±1.69% | +| MethodsProportionalBench | benchByCandidates | CPO STV,50 | 1 | 3 | 49.443mb | 0.014999s | ±5.16% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,50 | 1 | 3 | 28.048mb | 0.012300s | ±1.65% | +| MethodsProportionalBench | benchByCandidates | Jefferson,50 | 1 | 3 | 28.047mb | 0.011829s | ±4.08% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,50 | 1 | 3 | 28.047mb | 0.012519s | ±2.92% | +| MethodsProportionalBench | benchByCandidates | STV,60 | 1 | 3 | 57.963mb | 0.060343s | ±3.62% | +| MethodsProportionalBench | benchByCandidates | CPO STV,60 | 1 | 3 | 57.755mb | 0.019293s | ±5.08% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,60 | 1 | 3 | 32.772mb | 0.015561s | ±2.90% | +| MethodsProportionalBench | benchByCandidates | Jefferson,60 | 1 | 3 | 32.771mb | 0.014827s | ±0.42% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,60 | 1 | 3 | 32.772mb | 0.015001s | ±1.17% | +| MethodsProportionalBench | benchByCandidates | STV,70 | 1 | 3 | 79.765mb | 0.072319s | ±1.12% | +| MethodsProportionalBench | benchByCandidates | CPO STV,70 | 1 | 3 | 79.335mb | 0.024373s | ±3.34% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,70 | 1 | 3 | 45.391mb | 0.019033s | ±6.86% | +| MethodsProportionalBench | benchByCandidates | Jefferson,70 | 1 | 3 | 45.390mb | 0.018760s | ±5.18% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,70 | 1 | 3 | 45.391mb | 0.018700s | ±0.84% | +| MethodsProportionalBench | benchByCandidates | STV,80 | 1 | 3 | 88.501mb | 0.083748s | ±6.08% | +| MethodsProportionalBench | benchByCandidates | CPO STV,80 | 1 | 3 | 87.929mb | 0.027875s | ±2.13% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,80 | 1 | 3 | 50.453mb | 0.022614s | ±2.68% | +| MethodsProportionalBench | benchByCandidates | Jefferson,80 | 1 | 3 | 50.451mb | 0.022171s | ±0.40% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,80 | 1 | 3 | 50.452mb | 0.022119s | ±0.68% | +| MethodsProportionalBench | benchByCandidates | STV,90 | 1 | 3 | 97.257mb | 0.095371s | ±0.50% | +| MethodsProportionalBench | benchByCandidates | CPO STV,90 | 1 | 3 | 96.523mb | 0.032974s | ±2.87% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,90 | 1 | 3 | 55.586mb | 0.026290s | ±1.04% | +| MethodsProportionalBench | benchByCandidates | Jefferson,90 | 1 | 3 | 55.585mb | 0.026412s | ±3.99% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,90 | 1 | 3 | 55.586mb | 0.026185s | ±1.39% | +| MethodsProportionalBench | benchByCandidates | STV,100 | 1 | 3 | 105.949mb | 0.119077s | ±6.04% | +| MethodsProportionalBench | benchByCandidates | CPO STV,100 | 1 | 3 | 105.116mb | 0.041255s | ±5.16% | +| MethodsProportionalBench | benchByCandidates | Largest Remainder,100 | 1 | 3 | 60.703mb | 0.030632s | ±4.05% | +| MethodsProportionalBench | benchByCandidates | Jefferson,100 | 1 | 3 | 60.703mb | 0.030897s | ±0.77% | +| MethodsProportionalBench | benchByCandidates | Sainte-Laguë,100 | 1 | 3 | 60.703mb | 0.030627s | ±2.33% | ++--------------------------+-------------------+-----------------------+------+-----+-----------+-------------+---------+ + +# v4.0-beta1 Branch + +1000 random votes different for each test, variable number of candidates (look at column "set") + +* AMD Ryzen 9 5900X + +PHPBench (1.2.5) +with PHP version 8.1.6, xdebug ❌, opcache ✔ (with JIT Tracing) + +```php + #[Bench\Warmup(1)] + #[Bench\Iterations(3)] + #[Bench\Revs(1)] +``` +### Non-Proportional + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++-----------------------------+-------------------+-----------------------------------+------+-----+----------+-------------+----------+ +| benchmark | subject | set | revs | its | mem_peak | mode | rstdev | ++-----------------------------+-------------------+-----------------------------------+------+-----+----------+-------------+----------+ +| MethodsNonProportionalBench | benchByCandidates | BordaCount,3 | 1 | 3 | 4.271mb | 0.002924s | ±1.54% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,3 | 1 | 3 | 3.935mb | 0.000023s | ±13.03% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,3 | 1 | 3 | 3.863mb | 0.000020s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,3 | 1 | 3 | 3.864mb | 0.000024s | ±5.27% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,3 | 1 | 3 | 3.926mb | 0.001511s | ±1.33% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,3 | 1 | 3 | 3.932mb | 0.004448s | ±48.14% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,3 | 1 | 3 | 4.282mb | 0.000276s | ±138.24% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,3 | 1 | 3 | 3.929mb | 0.002349s | ±27.04% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,3 | 1 | 3 | 3.932mb | 0.004415s | ±1.34% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,3 | 1 | 3 | 3.864mb | 0.000024s | ±4.04% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,3 | 1 | 3 | 3.864mb | 0.000024s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,3 | 1 | 3 | 3.864mb | 0.000023s | ±3.55% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,3 | 1 | 3 | 3.934mb | 0.001575s | ±77.80% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,3 | 1 | 3 | 3.932mb | 0.000203s | ±135.55% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,3 | 1 | 3 | 3.865mb | 0.000027s | ±1.72% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,3 | 1 | 3 | 3.865mb | 0.000028s | ±2.92% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,3 | 1 | 3 | 3.865mb | 0.000029s | ±1.64% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,5 | 1 | 3 | 4.815mb | 0.002170s | ±28.16% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,5 | 1 | 3 | 4.755mb | 0.000030s | ±3.07% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,5 | 1 | 3 | 4.753mb | 0.000024s | ±1.94% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,5 | 1 | 3 | 4.755mb | 0.000030s | ±1.55% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,5 | 1 | 3 | 4.785mb | 0.002025s | ±8.04% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,5 | 1 | 3 | 4.817mb | 0.008432s | ±30.49% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,5 | 1 | 3 | 4.835mb | 0.002584s | ±63.87% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,5 | 1 | 3 | 4.815mb | 0.001811s | ±52.88% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,5 | 1 | 3 | 4.786mb | 0.003948s | ±1.11% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,5 | 1 | 3 | 4.755mb | 0.000031s | ±23.38% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,5 | 1 | 3 | 4.755mb | 0.000032s | ±3.94% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,5 | 1 | 3 | 4.755mb | 0.000030s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,5 | 1 | 3 | 4.837mb | 0.002302s | ±105.98% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,5 | 1 | 3 | 4.840mb | 0.000156s | ±123.44% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,5 | 1 | 3 | 4.816mb | 0.000036s | ±51.31% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,5 | 1 | 3 | 4.824mb | 0.000126s | ±132.19% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,5 | 1 | 3 | 4.819mb | 0.005626s | ±91.76% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,6 | 1 | 3 | 5.229mb | 0.002457s | ±5.62% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,6 | 1 | 3 | 5.269mb | 0.002489s | ±49.30% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,6 | 1 | 3 | 5.260mb | 0.001559s | ±78.69% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,6 | 1 | 3 | 5.264mb | 0.003050s | ±73.92% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,6 | 1 | 3 | 5.229mb | 0.002449s | ±3.15% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,6 | 1 | 3 | 5.262mb | 0.014201s | ±13.25% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,6 | 1 | 3 | 5.277mb | 0.001201s | ±1.27% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,6 | 1 | 3 | 5.229mb | 0.001869s | ±2.08% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,6 | 1 | 3 | 5.230mb | 0.004348s | ±1.65% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,6 | 1 | 3 | 5.261mb | 0.000115s | ±133.60% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,6 | 1 | 3 | 5.260mb | 0.001349s | ±68.59% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,6 | 1 | 3 | 5.201mb | 0.000027s | ±5.05% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,6 | 1 | 3 | 5.295mb | 0.002948s | ±42.04% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,6 | 1 | 3 | 5.228mb | 0.000107s | ±14.62% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,6 | 1 | 3 | 5.201mb | 0.000044s | ±18.40% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,6 | 1 | 3 | 5.201mb | 0.000046s | ±3.75% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,6 | 1 | 3 | 5.267mb | 0.009940s | ±22.86% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,7 | 1 | 3 | 5.674mb | 0.002877s | ±4.27% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,7 | 1 | 3 | 5.646mb | 0.000032s | ±5.10% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,7 | 1 | 3 | 5.643mb | 0.000025s | ±1.91% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,7 | 1 | 3 | 5.646mb | 0.000030s | ±7.44% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,7 | 1 | 3 | 5.674mb | 0.002809s | ±2.26% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,7 | 1 | 3 | 5.743mb | 0.014805s | ±4.45% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,7 | 1 | 3 | 5.723mb | 0.010580s | ±6.88% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,7 | 1 | 3 | 5.705mb | 0.002164s | ±48.66% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,7 | 1 | 3 | 5.674mb | 0.005069s | ±3.11% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,7 | 1 | 3 | 5.705mb | 0.001430s | ±68.26% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,7 | 1 | 3 | 5.706mb | 0.000121s | ±132.79% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,7 | 1 | 3 | 5.646mb | 0.000030s | ±4.56% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,7 | 1 | 3 | 5.726mb | 0.002715s | ±34.92% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,7 | 1 | 3 | 5.687mb | 0.000184s | ±12.97% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,7 | 1 | 3 | 5.647mb | 0.000051s | ±0.93% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,7 | 1 | 3 | 5.647mb | 0.000056s | ±4.34% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,7 | 1 | 3 | 5.716mb | 0.002377s | ±106.81% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,8 | 1 | 3 | 6.438mb | 0.003243s | ±5.05% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,8 | 1 | 3 | 6.470mb | 0.000122s | ±131.50% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,8 | 1 | 3 | 6.470mb | 0.001435s | ±82.71% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,8 | 1 | 3 | 6.413mb | 0.000031s | ±8.12% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,8 | 1 | 3 | 6.438mb | 0.002999s | ±3.19% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,8 | 1 | 3 | 6.474mb | 0.018136s | ±4.09% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,8 | 1 | 3 | 6.489mb | 0.104723s | ±0.40% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,8 | 1 | 3 | 6.439mb | 0.002409s | ±9.94% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,8 | 1 | 3 | 6.439mb | 0.005636s | ±2.19% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,8 | 1 | 3 | 6.411mb | 0.000034s | ±7.20% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,8 | 1 | 3 | 6.411mb | 0.000032s | ±14.19% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,8 | 1 | 3 | 6.413mb | 0.000031s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,8 | 1 | 3 | 6.465mb | 0.000189s | ±16.95% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,8 | 1 | 3 | 6.463mb | 0.000203s | ±9.52% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,8 | 1 | 3 | 6.469mb | 0.000142s | ±125.75% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,8 | 1 | 3 | 6.414mb | 0.000061s | ±2.28% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,8 | 1 | 3 | 6.469mb | 0.003305s | ±101.92% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,9 | 1 | 3 | 6.897mb | 0.003411s | ±3.49% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,9 | 1 | 3 | 6.873mb | 0.000036s | ±4.04% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,9 | 1 | 3 | 6.872mb | 0.000029s | ±2.82% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,9 | 1 | 3 | 6.873mb | 0.000034s | ±4.04% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,9 | 1 | 3 | 6.897mb | 0.003076s | ±2.97% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,9 | 1 | 3 | 6.930mb | 0.021697s | ±5.03% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,9 | 1 | 3 | 6.950mb | 1.160851s | ±0.82% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,9 | 1 | 3 | 6.897mb | 0.002320s | ±5.76% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,9 | 1 | 3 | 6.897mb | 0.005771s | ±3.65% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,9 | 1 | 3 | 6.873mb | 0.000036s | ±8.41% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,9 | 1 | 3 | 6.930mb | 0.000122s | ±131.63% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,9 | 1 | 3 | 6.873mb | 0.000036s | ±3.50% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,9 | 1 | 3 | 6.951mb | 0.000457s | ±85.52% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,9 | 1 | 3 | 6.943mb | 0.000356s | ±12.93% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,9 | 1 | 3 | 6.876mb | 0.000076s | ±3.22% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,9 | 1 | 3 | 6.876mb | 0.000079s | ±2.59% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,9 | 1 | 3 | 6.928mb | 0.001263s | ±103.98% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,10 | 1 | 3 | 7.344mb | 0.003596s | ±2.72% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,10 | 1 | 3 | 7.321mb | 0.000037s | ±3.40% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,10 | 1 | 3 | 7.321mb | 0.000030s | ±3.21% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,10 | 1 | 3 | 7.322mb | 0.000039s | ±39.55% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,10 | 1 | 3 | 7.344mb | 0.003367s | ±3.86% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,10 | 1 | 3 | 7.352mb | 0.023994s | ±0.42% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,10 | 1 | 3 | 7.400mb | 14.549329s | ±0.38% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,10 | 1 | 3 | 7.344mb | 0.002413s | ±1.69% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,10 | 1 | 3 | 7.344mb | 0.006122s | ±0.24% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,10 | 1 | 3 | 7.322mb | 0.000036s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,10 | 1 | 3 | 7.322mb | 0.000036s | ±3.82% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,10 | 1 | 3 | 7.322mb | 0.000040s | ±4.32% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,10 | 1 | 3 | 7.402mb | 0.000546s | ±6.04% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,10 | 1 | 3 | 7.402mb | 0.000592s | ±12.46% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,10 | 1 | 3 | 7.329mb | 0.000089s | ±10.25% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,10 | 1 | 3 | 7.329mb | 0.000089s | ±2.30% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,10 | 1 | 3 | 7.329mb | 0.000217s | ±109.63% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,11 | 1 | 3 | 7.790mb | 0.003812s | ±2.17% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,11 | 1 | 3 | 7.768mb | 0.000038s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,11 | 1 | 3 | 7.767mb | 0.000055s | ±41.31% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,11 | 1 | 3 | 7.769mb | 0.000038s | ±2.15% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,11 | 1 | 3 | 7.790mb | 0.003964s | ±0.42% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,11 | 1 | 3 | 7.798mb | 0.028704s | ±1.44% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,11 | 1 | 3 | 7.849mb | 193.044781s | ±1.45% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,11 | 1 | 3 | 7.790mb | 0.002761s | ±3.59% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,11 | 1 | 3 | 7.790mb | 0.007038s | ±3.48% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,11 | 1 | 3 | 7.769mb | 0.000036s | ±7.44% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,11 | 1 | 3 | 7.769mb | 0.000040s | ±6.13% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,11 | 1 | 3 | 7.769mb | 0.000041s | ±6.94% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,11 | 1 | 3 | 7.866mb | 0.000817s | ±1.51% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,11 | 1 | 3 | 7.862mb | 0.000724s | ±9.95% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,11 | 1 | 3 | 7.776mb | 0.000104s | ±3.36% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,11 | 1 | 3 | 7.777mb | 0.000110s | ±7.03% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,11 | 1 | 3 | 7.777mb | 0.000160s | ±4.44% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,20 | 1 | 3 | 12.511mb | 0.007010s | ±0.76% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,20 | 1 | 3 | 12.503mb | 0.000066s | ±2.55% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,20 | 1 | 3 | 12.507mb | 0.000050s | ±1.63% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,20 | 1 | 3 | 12.504mb | 0.000067s | ±9.96% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,20 | 1 | 3 | 12.511mb | 0.007244s | ±1.16% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,20 | 1 | 3 | 12.533mb | 0.090596s | ±1.12% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,20 | 1 | 3 | 12.474mb | 0.000007s | ±22.17% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,20 | 1 | 3 | 12.510mb | 0.004687s | ±1.64% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,20 | 1 | 3 | 12.510mb | 0.012116s | ±0.28% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,20 | 1 | 3 | 12.503mb | 0.000067s | ±1.22% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,20 | 1 | 3 | 12.502mb | 0.000153s | ±122.93% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,20 | 1 | 3 | 12.503mb | 0.000066s | ±2.11% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,20 | 1 | 3 | 12.785mb | 0.011022s | ±11.77% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,20 | 1 | 3 | 12.802mb | 0.012487s | ±0.22% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,20 | 1 | 3 | 12.542mb | 0.000431s | ±1.23% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,20 | 1 | 3 | 12.542mb | 0.000454s | ±2.35% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,20 | 1 | 3 | 12.542mb | 0.000775s | ±2.63% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,30 | 1 | 3 | 17.044mb | 0.010831s | ±0.89% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,30 | 1 | 3 | 17.047mb | 0.000111s | ±22.90% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,30 | 1 | 3 | 17.054mb | 0.000072s | ±21.70% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,30 | 1 | 3 | 17.048mb | 0.000102s | ±2.01% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,30 | 1 | 3 | 17.044mb | 0.010428s | ±1.02% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,30 | 1 | 3 | 17.080mb | 0.193523s | ±4.90% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,30 | 1 | 3 | 17.007mb | 0.000009s | ±21.60% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,30 | 1 | 3 | 17.043mb | 0.006859s | ±1.91% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,30 | 1 | 3 | 17.043mb | 0.018535s | ±0.59% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,30 | 1 | 3 | 17.046mb | 0.000192s | ±114.86% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,30 | 1 | 3 | 17.045mb | 0.000108s | ±1.16% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,30 | 1 | 3 | 17.046mb | 0.000108s | ±2.32% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,30 | 1 | 3 | 17.660mb | 0.101841s | ±12.55% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,30 | 1 | 3 | 17.667mb | 0.099423s | ±5.12% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,30 | 1 | 3 | 17.219mb | 0.001361s | ±3.04% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,30 | 1 | 3 | 17.216mb | 0.001365s | ±7.51% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,30 | 1 | 3 | 17.103mb | 0.002616s | ±3.81% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,40 | 1 | 3 | 23.088mb | 0.015801s | ±0.85% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,40 | 1 | 3 | 23.098mb | 0.000158s | ±0.30% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,40 | 1 | 3 | 23.137mb | 0.000108s | ±0.88% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,40 | 1 | 3 | 23.100mb | 0.000165s | ±23.09% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,40 | 1 | 3 | 23.088mb | 0.014674s | ±1.13% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,40 | 1 | 3 | 23.190mb | 0.334868s | ±5.42% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,40 | 1 | 3 | 23.045mb | 0.000008s | ±16.27% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,40 | 1 | 3 | 23.075mb | 0.009288s | ±2.02% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,40 | 1 | 3 | 23.075mb | 0.025577s | ±0.95% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,40 | 1 | 3 | 23.089mb | 0.000154s | ±16.71% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,40 | 1 | 3 | 23.090mb | 0.000154s | ±1.90% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,40 | 1 | 3 | 23.089mb | 0.000152s | ±1.92% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,40 | 1 | 3 | 24.218mb | 0.440535s | ±11.54% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,40 | 1 | 3 | 24.196mb | 0.459768s | ±6.19% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,40 | 1 | 3 | 23.269mb | 0.002907s | ±1.89% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,40 | 1 | 3 | 23.272mb | 0.003054s | ±0.85% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,40 | 1 | 3 | 23.270mb | 0.005822s | ±1.07% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,50 | 1 | 3 | 27.651mb | 0.020448s | ±0.99% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,50 | 1 | 3 | 27.665mb | 0.000216s | ±0.88% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,50 | 1 | 3 | 27.722mb | 0.000133s | ±0.71% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,50 | 1 | 3 | 27.668mb | 0.000215s | ±2.19% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,50 | 1 | 3 | 27.652mb | 0.019335s | ±0.66% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,50 | 1 | 3 | 27.784mb | 0.470916s | ±10.21% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,50 | 1 | 3 | 27.601mb | 0.000006s | ±20.20% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,50 | 1 | 3 | 27.631mb | 0.011887s | ±1.77% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,50 | 1 | 3 | 27.631mb | 0.033005s | ±0.23% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,50 | 1 | 3 | 27.653mb | 0.000218s | ±13.74% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,50 | 1 | 3 | 27.659mb | 0.000212s | ±1.45% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,50 | 1 | 3 | 27.661mb | 0.000210s | ±1.33% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,50 | 1 | 3 | 29.349mb | 1.454932s | ±5.84% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,50 | 1 | 3 | 29.321mb | 1.577493s | ±11.78% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,50 | 1 | 3 | 27.886mb | 0.005544s | ±1.19% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,50 | 1 | 3 | 27.886mb | 0.005489s | ±3.28% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,50 | 1 | 3 | 27.887mb | 0.011167s | ±1.39% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,60 | 1 | 3 | 32.216mb | 0.025406s | ±0.36% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,60 | 1 | 3 | 32.229mb | 0.000284s | ±1.35% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,60 | 1 | 3 | 32.303mb | 0.000176s | ±22.99% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,60 | 1 | 3 | 32.235mb | 0.000286s | ±2.46% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,60 | 1 | 3 | 32.216mb | 0.024220s | ±1.01% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,60 | 1 | 3 | 32.383mb | 0.774454s | ±5.71% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,60 | 1 | 3 | 32.157mb | 0.000006s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,60 | 1 | 3 | 32.186mb | 0.014692s | ±1.33% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,60 | 1 | 3 | 32.186mb | 0.041035s | ±0.53% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,60 | 1 | 3 | 32.223mb | 0.000283s | ±2.00% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,60 | 1 | 3 | 32.221mb | 0.000282s | ±1.02% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,60 | 1 | 3 | 32.214mb | 0.000281s | ±5.60% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,60 | 1 | 3 | 34.635mb | 3.743467s | ±5.62% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,60 | 1 | 3 | 34.706mb | 4.070218s | ±5.76% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,60 | 1 | 3 | 32.495mb | 0.009350s | ±1.08% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,60 | 1 | 3 | 32.497mb | 0.009517s | ±2.41% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,60 | 1 | 3 | 32.498mb | 0.019421s | ±0.57% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,70 | 1 | 3 | 44.439mb | 0.032305s | ±1.34% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,70 | 1 | 3 | 44.434mb | 0.000380s | ±0.81% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,70 | 1 | 3 | 44.720mb | 0.000224s | ±3.00% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,70 | 1 | 3 | 44.462mb | 0.000373s | ±2.55% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,70 | 1 | 3 | 44.439mb | 0.030708s | ±2.96% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,70 | 1 | 3 | 44.823mb | 1.023154s | ±6.37% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,70 | 1 | 3 | 44.361mb | 0.000008s | ±16.27% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,70 | 1 | 3 | 44.392mb | 0.019544s | ±1.65% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,70 | 1 | 3 | 44.392mb | 0.052625s | ±0.48% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,70 | 1 | 3 | 44.420mb | 0.000353s | ±1.61% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,70 | 1 | 3 | 44.423mb | 0.000370s | ±7.45% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,70 | 1 | 3 | 44.422mb | 0.000363s | ±0.67% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,70 | 1 | 3 | 47.656mb | 6.784191s | ±1.86% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,70 | 1 | 3 | 47.611mb | 6.588119s | ±6.27% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,70 | 1 | 3 | 45.530mb | 0.015970s | ±6.81% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,70 | 1 | 3 | 45.534mb | 0.016065s | ±2.66% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,70 | 1 | 3 | 45.529mb | 0.031048s | ±0.63% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,80 | 1 | 3 | 49.285mb | 0.038611s | ±0.73% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,80 | 1 | 3 | 49.281mb | 0.000463s | ±3.47% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,80 | 1 | 3 | 49.633mb | 0.000269s | ±14.00% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,80 | 1 | 3 | 49.311mb | 0.000468s | ±19.71% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,80 | 1 | 3 | 49.285mb | 0.038735s | ±1.24% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,80 | 1 | 3 | 49.632mb | 1.428535s | ±11.94% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,80 | 1 | 3 | 49.199mb | 0.000008s | ±23.18% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,80 | 1 | 3 | 49.233mb | 0.022248s | ±1.47% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,80 | 1 | 3 | 49.233mb | 0.063431s | ±0.89% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,80 | 1 | 3 | 49.266mb | 0.000446s | ±0.49% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,80 | 1 | 3 | 49.267mb | 0.000454s | ±2.22% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,80 | 1 | 3 | 49.264mb | 0.000452s | ±0.82% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,80 | 1 | 3 | 53.517mb | 18.839536s | ±21.46% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,80 | 1 | 3 | 53.492mb | 16.246016s | ±14.55% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,80 | 1 | 3 | 50.527mb | 0.022245s | ±0.63% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,80 | 1 | 3 | 50.532mb | 0.023183s | ±2.02% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,80 | 1 | 3 | 50.534mb | 0.046185s | ±0.61% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,90 | 1 | 3 | 54.130mb | 0.046329s | ±1.19% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,90 | 1 | 3 | 54.154mb | 0.000574s | ±2.18% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,90 | 1 | 3 | 54.497mb | 0.000318s | ±7.27% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,90 | 1 | 3 | 54.160mb | 0.000607s | ±3.88% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,90 | 1 | 3 | 54.130mb | 0.045796s | ±1.57% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,90 | 1 | 3 | 54.478mb | 1.483854s | ±10.63% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,90 | 1 | 3 | 54.036mb | 0.000007s | ±31.50% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,90 | 1 | 3 | 54.074mb | 0.026423s | ±1.50% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,90 | 1 | 3 | 54.074mb | 0.075511s | ±0.51% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,90 | 1 | 3 | 54.109mb | 0.000619s | ±7.23% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,90 | 1 | 3 | 54.107mb | 0.000535s | ±0.95% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,90 | 1 | 3 | 54.111mb | 0.000554s | ±0.52% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,90 | 1 | 3 | 59.327mb | 26.526442s | ±14.83% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,90 | 1 | 3 | 59.269mb | 28.957465s | ±10.13% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,90 | 1 | 3 | 55.537mb | 0.031824s | ±1.01% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,90 | 1 | 3 | 55.537mb | 0.031800s | ±3.34% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,90 | 1 | 3 | 55.536mb | 0.063706s | ±0.29% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,100 | 1 | 3 | 58.976mb | 0.054073s | ±1.33% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,100 | 1 | 3 | 58.999mb | 0.000670s | ±0.63% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,100 | 1 | 3 | 59.394mb | 0.000375s | ±0.65% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,100 | 1 | 3 | 59.010mb | 0.000691s | ±0.96% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,100 | 1 | 3 | 58.976mb | 0.052387s | ±1.49% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,100 | 1 | 3 | 59.436mb | 1.900686s | ±7.49% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,100 | 1 | 3 | 58.873mb | 0.000006s | ±13.61% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,100 | 1 | 3 | 58.916mb | 0.030921s | ±3.30% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,100 | 1 | 3 | 58.916mb | 0.086447s | ±0.74% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,100 | 1 | 3 | 58.953mb | 0.000642s | ±1.26% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,100 | 1 | 3 | 58.950mb | 0.000636s | ±0.52% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,100 | 1 | 3 | 58.952mb | 0.000641s | ±0.89% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,100 | 1 | 3 | 65.323mb | 49.233827s | ±3.59% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,100 | 1 | 3 | 65.547mb | 55.108877s | ±14.49% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,100 | 1 | 3 | 60.537mb | 0.043607s | ±0.64% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,100 | 1 | 3 | 60.534mb | 0.043322s | ±1.61% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,100 | 1 | 3 | 60.537mb | 0.089272s | ±6.19% | ++-----------------------------+-------------------+-----------------------------------+------+-----+----------+-------------+----------+ + +### Proportional + ++--------------------------+-------------------+-------------+------+-----+-----------+-------------+---------+ +| benchmark | subject | set | revs | its | mem_peak | mode | rstdev | ++--------------------------+-------------------+-------------+------+-----+-----------+-------------+---------+ +| MethodsProportionalBench | benchByCandidates | STV,3 | 1 | 3 | 5.829mb | 0.002659s | ±7.56% | +| MethodsProportionalBench | benchByCandidates | CPO STV,3 | 1 | 3 | 5.964mb | 0.014000s | ±73.36% | +| MethodsProportionalBench | benchByCandidates | STV,4 | 1 | 3 | 6.649mb | 0.003441s | ±0.53% | +| MethodsProportionalBench | benchByCandidates | CPO STV,4 | 1 | 3 | 6.770mb | 0.018359s | ±41.91% | +| MethodsProportionalBench | benchByCandidates | STV,5 | 1 | 3 | 7.470mb | 0.004373s | ±10.45% | +| MethodsProportionalBench | benchByCandidates | CPO STV,5 | 1 | 3 | 7.669mb | 0.021245s | ±31.55% | +| MethodsProportionalBench | benchByCandidates | STV,6 | 1 | 3 | 8.292mb | 0.005903s | ±11.56% | +| MethodsProportionalBench | benchByCandidates | CPO STV,6 | 1 | 3 | 10.407mb | 0.166816s | ±5.62% | +| MethodsProportionalBench | benchByCandidates | STV,7 | 1 | 3 | 9.143mb | 0.009377s | ±19.86% | +| MethodsProportionalBench | benchByCandidates | CPO STV,7 | 1 | 3 | 13.332mb | 0.328804s | ±0.72% | +| MethodsProportionalBench | benchByCandidates | STV,8 | 1 | 3 | 10.573mb | 0.007267s | ±3.74% | +| MethodsProportionalBench | benchByCandidates | CPO STV,8 | 1 | 3 | 18.034mb | 0.611943s | ±1.64% | +| MethodsProportionalBench | benchByCandidates | STV,9 | 1 | 3 | 11.409mb | 0.007971s | ±17.82% | +| MethodsProportionalBench | benchByCandidates | CPO STV,9 | 1 | 3 | 83.441mb | 5.551505s | ±1.05% | +| MethodsProportionalBench | benchByCandidates | STV,10 | 1 | 3 | 12.233mb | 0.008690s | ±3.30% | +| MethodsProportionalBench | benchByCandidates | CPO STV,10 | 1 | 3 | 155.667mb | 11.802237s | ±0.17% | +| MethodsProportionalBench | benchByCandidates | STV,11 | 1 | 3 | 13.056mb | 0.009754s | ±1.28% | +| MethodsProportionalBench | benchByCandidates | CPO STV,11 | 1 | 3 | 286.714mb | 23.326424s | ±0.37% | +| MethodsProportionalBench | benchByCandidates | STV,12 | 1 | 3 | 13.878mb | 0.010488s | ±4.76% | +| MethodsProportionalBench | benchByCandidates | CPO STV,12 | 1 | 3 | 2.417gb | 287.180966s | ±13.06% | +| MethodsProportionalBench | benchByCandidates | STV,13 | 1 | 3 | 14.700mb | 0.015853s | ±1.94% | +| MethodsProportionalBench | benchByCandidates | CPO STV,13 | 1 | 3 | 14.666mb | 0.004435s | ±6.87% | +| MethodsProportionalBench | benchByCandidates | STV,14 | 1 | 3 | 15.524mb | 0.016821s | ±2.48% | +| MethodsProportionalBench | benchByCandidates | CPO STV,14 | 1 | 3 | 15.487mb | 0.005004s | ±11.10% | +| MethodsProportionalBench | benchByCandidates | STV,15 | 1 | 3 | 16.345mb | 0.016804s | ±5.20% | +| MethodsProportionalBench | benchByCandidates | CPO STV,15 | 1 | 3 | 16.309mb | 0.005149s | ±10.15% | +| MethodsProportionalBench | benchByCandidates | STV,16 | 1 | 3 | 18.447mb | 0.017804s | ±8.62% | +| MethodsProportionalBench | benchByCandidates | CPO STV,16 | 1 | 3 | 18.411mb | 0.005673s | ±11.23% | +| MethodsProportionalBench | benchByCandidates | STV,17 | 1 | 3 | 19.327mb | 0.018709s | ±2.85% | +| MethodsProportionalBench | benchByCandidates | CPO STV,17 | 1 | 3 | 19.287mb | 0.005789s | ±6.55% | +| MethodsProportionalBench | benchByCandidates | STV,18 | 1 | 3 | 20.157mb | 0.018876s | ±2.92% | +| MethodsProportionalBench | benchByCandidates | CPO STV,18 | 1 | 3 | 20.115mb | 0.006241s | ±3.11% | +| MethodsProportionalBench | benchByCandidates | STV,19 | 1 | 3 | 20.982mb | 0.022289s | ±4.29% | +| MethodsProportionalBench | benchByCandidates | CPO STV,19 | 1 | 3 | 20.940mb | 0.005274s | ±8.26% | +| MethodsProportionalBench | benchByCandidates | STV,20 | 1 | 3 | 21.809mb | 0.022712s | ±1.60% | +| MethodsProportionalBench | benchByCandidates | CPO STV,20 | 1 | 3 | 21.765mb | 0.005522s | ±8.89% | +| MethodsProportionalBench | benchByCandidates | STV,30 | 1 | 3 | 30.112mb | 0.032666s | ±4.21% | +| MethodsProportionalBench | benchByCandidates | CPO STV,30 | 1 | 3 | 30.054mb | 0.008870s | ±6.10% | +| MethodsProportionalBench | benchByCandidates | STV,40 | 1 | 3 | 41.229mb | 0.044671s | ±1.33% | +| MethodsProportionalBench | benchByCandidates | CPO STV,40 | 1 | 3 | 41.131mb | 0.011938s | ±6.82% | +| MethodsProportionalBench | benchByCandidates | STV,50 | 1 | 3 | 49.601mb | 0.057759s | ±2.21% | +| MethodsProportionalBench | benchByCandidates | CPO STV,50 | 1 | 3 | 49.443mb | 0.013495s | ±6.86% | +| MethodsProportionalBench | benchByCandidates | STV,60 | 1 | 3 | 57.954mb | 0.068025s | ±0.86% | +| MethodsProportionalBench | benchByCandidates | CPO STV,60 | 1 | 3 | 57.755mb | 0.018584s | ±4.75% | +| MethodsProportionalBench | benchByCandidates | STV,70 | 1 | 3 | 79.813mb | 0.083113s | ±0.45% | +| MethodsProportionalBench | benchByCandidates | CPO STV,70 | 1 | 3 | 79.335mb | 0.022136s | ±3.20% | +| MethodsProportionalBench | benchByCandidates | STV,80 | 1 | 3 | 88.545mb | 0.096366s | ±1.30% | +| MethodsProportionalBench | benchByCandidates | CPO STV,80 | 1 | 3 | 87.929mb | 0.026070s | ±2.65% | +| MethodsProportionalBench | benchByCandidates | STV,90 | 1 | 3 | 97.297mb | 0.111921s | ±0.16% | +| MethodsProportionalBench | benchByCandidates | CPO STV,90 | 1 | 3 | 96.523mb | 0.031376s | ±2.81% | +| MethodsProportionalBench | benchByCandidates | STV,100 | 1 | 3 | 106.003mb | 0.127644s | ±1.99% | +| MethodsProportionalBench | benchByCandidates | CPO STV,100 | 1 | 3 | 105.116mb | 0.035404s | ±1.94% | ++--------------------------+-------------------+-------------+------+-----+-----------+-------------+---------+ + +# v4.0-alpha1 Branch + +1000 random votes different for each test, variable number of candidates (look at column "set") + +* AMD Ryzen 9 5900X + +PHPBench (1.2.5) +with PHP version 8.1.6, xdebug ❌, opcache ✔ (with JIT Tracing) + +```php + #[Bench\Warmup(1)] + #[Bench\Iterations(10)] + #[Bench\Revs(1)] +``` +### Non-Proportional + + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ +| benchmark | subject | set | revs | its | mem_peak | mode | rstdev | ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ +| MethodsNonProportionalBench | benchByCandidates | BordaCount,3 | 1 | 10 | 4.267mb | 0.001576s | ±28.18% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,3 | 1 | 10 | 3.934mb | 0.000024s | ±11.86% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,3 | 1 | 10 | 3.861mb | 0.000020s | ±5.84% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,3 | 1 | 10 | 3.863mb | 0.000024s | ±3.35% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,3 | 1 | 10 | 3.925mb | 0.001621s | ±12.99% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,3 | 1 | 10 | 3.931mb | 0.003948s | ±43.00% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,3 | 1 | 10 | 3.937mb | 0.000084s | ±177.58% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,3 | 1 | 10 | 3.928mb | 0.001411s | ±3.14% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,3 | 1 | 10 | 3.931mb | 0.003039s | ±17.49% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,3 | 1 | 10 | 3.863mb | 0.000024s | ±3.95% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,3 | 1 | 10 | 3.863mb | 0.000023s | ±3.92% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,3 | 1 | 10 | 3.863mb | 0.000025s | ±2.72% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,3 | 1 | 10 | 3.932mb | 0.000036s | ±186.95% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,3 | 1 | 10 | 3.868mb | 0.000037s | ±8.30% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,3 | 1 | 10 | 3.863mb | 0.000028s | ±3.09% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,3 | 1 | 10 | 3.863mb | 0.000030s | ±24.61% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,3 | 1 | 10 | 3.863mb | 0.000030s | ±1.54% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,5 | 1 | 10 | 4.814mb | 0.002172s | ±18.53% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,5 | 1 | 10 | 4.754mb | 0.000030s | ±6.13% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,5 | 1 | 10 | 4.814mb | 0.000025s | ±190.78% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,5 | 1 | 10 | 4.754mb | 0.000030s | ±16.08% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,5 | 1 | 10 | 4.784mb | 0.002020s | ±2.71% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,5 | 1 | 10 | 4.818mb | 0.008122s | ±30.49% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,5 | 1 | 10 | 4.946mb | 0.000265s | ±2.46% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,5 | 1 | 10 | 4.815mb | 0.001705s | ±45.21% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,5 | 1 | 10 | 4.816mb | 0.003976s | ±9.82% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,5 | 1 | 10 | 4.754mb | 0.000032s | ±3.82% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,5 | 1 | 10 | 4.754mb | 0.000030s | ±3.82% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,5 | 1 | 10 | 4.754mb | 0.000030s | ±2.54% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,5 | 1 | 10 | 4.836mb | 0.000607s | ±200.45% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,5 | 1 | 10 | 4.774mb | 0.000071s | ±4.95% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,5 | 1 | 10 | 4.824mb | 0.000788s | ±133.97% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,5 | 1 | 10 | 4.754mb | 0.000043s | ±22.29% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,5 | 1 | 10 | 4.817mb | 0.002600s | ±107.48% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,6 | 1 | 10 | 5.228mb | 0.002455s | ±3.85% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,6 | 1 | 10 | 5.267mb | 0.000087s | ±126.33% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,6 | 1 | 10 | 5.197mb | 0.000026s | ±7.69% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,6 | 1 | 10 | 5.200mb | 0.000030s | ±3.57% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,6 | 1 | 10 | 5.228mb | 0.002404s | ±3.31% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,6 | 1 | 10 | 5.297mb | 0.010311s | ±0.64% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,6 | 1 | 10 | 5.981mb | 0.001599s | ±14.74% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,6 | 1 | 10 | 5.228mb | 0.001897s | ±2.98% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,6 | 1 | 10 | 5.229mb | 0.004465s | ±2.30% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,6 | 1 | 10 | 5.200mb | 0.000030s | ±6.99% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,6 | 1 | 10 | 5.200mb | 0.000030s | ±7.99% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,6 | 1 | 10 | 5.200mb | 0.000031s | ±5.87% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,6 | 1 | 10 | 5.292mb | 0.000143s | ±194.55% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,6 | 1 | 10 | 5.229mb | 0.000097s | ±10.28% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,6 | 1 | 10 | 5.267mb | 0.000367s | ±164.46% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,6 | 1 | 10 | 5.262mb | 0.000041s | ±258.69% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,6 | 1 | 10 | 5.259mb | 0.001026s | ±120.33% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,7 | 1 | 10 | 5.672mb | 0.002705s | ±4.59% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,7 | 1 | 10 | 5.708mb | 0.000048s | ±95.75% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,7 | 1 | 10 | 5.712mb | 0.000031s | ±246.98% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,7 | 1 | 10 | 5.705mb | 0.000032s | ±180.86% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,7 | 1 | 10 | 5.673mb | 0.002667s | ±2.17% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,7 | 1 | 10 | 5.712mb | 0.013131s | ±4.15% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,7 | 1 | 10 | 11.331mb | 0.013923s | ±5.26% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,7 | 1 | 10 | 5.673mb | 0.002031s | ±2.42% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,7 | 1 | 10 | 5.673mb | 0.004654s | ±1.60% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,7 | 1 | 10 | 5.704mb | 0.000036s | ±142.26% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,7 | 1 | 10 | 5.645mb | 0.000032s | ±3.34% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,7 | 1 | 10 | 5.645mb | 0.000033s | ±4.84% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,7 | 1 | 10 | 5.749mb | 0.000365s | ±128.92% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,7 | 1 | 10 | 5.688mb | 0.000138s | ±9.68% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,7 | 1 | 10 | 5.703mb | 0.000051s | ±169.59% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,7 | 1 | 10 | 5.703mb | 0.000052s | ±219.33% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,7 | 1 | 10 | 5.703mb | 0.000408s | ±146.29% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,8 | 1 | 10 | 6.437mb | 0.002997s | ±12.75% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,8 | 1 | 10 | 6.412mb | 0.000034s | ±4.63% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,8 | 1 | 10 | 6.468mb | 0.000031s | ±144.07% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,8 | 1 | 10 | 6.412mb | 0.000034s | ±4.84% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,8 | 1 | 10 | 6.437mb | 0.002964s | ±2.69% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,8 | 1 | 10 | 6.475mb | 0.016236s | ±6.00% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,8 | 1 | 10 | 83.245mb | 0.150337s | ±1.54% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,8 | 1 | 10 | 6.475mb | 0.002121s | ±19.90% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,8 | 1 | 10 | 6.437mb | 0.005121s | ±12.26% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,8 | 1 | 10 | 6.412mb | 0.000034s | ±12.26% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,8 | 1 | 10 | 6.412mb | 0.000036s | ±4.23% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,8 | 1 | 10 | 6.412mb | 0.000036s | ±4.70% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,8 | 1 | 10 | 6.467mb | 0.000255s | ±11.85% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,8 | 1 | 10 | 6.468mb | 0.000245s | ±9.11% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,8 | 1 | 10 | 6.466mb | 0.000063s | ±207.41% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,8 | 1 | 10 | 6.412mb | 0.000065s | ±3.48% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,8 | 1 | 10 | 6.467mb | 0.001233s | ±129.54% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,9 | 1 | 10 | 6.896mb | 0.003210s | ±2.84% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,9 | 1 | 10 | 6.926mb | 0.000036s | ±238.83% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,9 | 1 | 10 | 6.870mb | 0.000030s | ±5.64% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,9 | 1 | 10 | 6.872mb | 0.000036s | ±1.86% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,9 | 1 | 10 | 6.896mb | 0.003026s | ±2.54% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,9 | 1 | 10 | 6.926mb | 0.019699s | ±2.99% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,9 | 1 | 10 | 672.151mb | 1.652125s | ±0.27% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,9 | 1 | 10 | 6.895mb | 0.002209s | ±3.77% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,9 | 1 | 10 | 6.895mb | 0.005537s | ±1.11% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,9 | 1 | 10 | 6.872mb | 0.000037s | ±14.11% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,9 | 1 | 10 | 6.872mb | 0.000036s | ±4.33% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,9 | 1 | 10 | 6.872mb | 0.000036s | ±5.63% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,9 | 1 | 10 | 6.952mb | 0.000362s | ±91.18% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,9 | 1 | 10 | 6.938mb | 0.000309s | ±14.39% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,9 | 1 | 10 | 6.931mb | 0.000078s | ±157.32% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,9 | 1 | 10 | 6.875mb | 0.000081s | ±26.92% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,9 | 1 | 10 | 6.928mb | 0.000917s | ±105.36% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,10 | 1 | 10 | 7.343mb | 0.003537s | ±3.00% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,10 | 1 | 10 | 7.385mb | 0.000046s | ±194.34% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,10 | 1 | 10 | 7.319mb | 0.000034s | ±4.46% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,10 | 1 | 10 | 7.321mb | 0.000038s | ±11.93% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,10 | 1 | 10 | 7.343mb | 0.003360s | ±8.59% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,10 | 1 | 10 | 7.350mb | 0.023538s | ±0.45% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,10 | 1 | 10 | 7.368mb | 0.000007s | ±15.07% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,10 | 1 | 10 | 7.343mb | 0.002383s | ±3.89% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,10 | 1 | 10 | 7.343mb | 0.006007s | ±1.82% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,10 | 1 | 10 | 7.321mb | 0.000041s | ±38.78% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,10 | 1 | 10 | 7.321mb | 0.000041s | ±3.66% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,10 | 1 | 10 | 7.321mb | 0.000039s | ±3.33% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,10 | 1 | 10 | 7.403mb | 0.000503s | ±12.11% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,10 | 1 | 10 | 7.403mb | 0.000520s | ±14.33% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,10 | 1 | 10 | 7.328mb | 0.000089s | ±1.48% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,10 | 1 | 10 | 7.328mb | 0.000094s | ±1.89% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,10 | 1 | 10 | 7.375mb | 0.001436s | ±114.24% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,20 | 1 | 10 | 12.566mb | 0.006712s | ±12.42% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,20 | 1 | 10 | 12.563mb | 0.000081s | ±139.57% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,20 | 1 | 10 | 12.507mb | 0.000054s | ±188.52% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,20 | 1 | 10 | 12.536mb | 0.000067s | ±205.54% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,20 | 1 | 10 | 12.510mb | 0.006443s | ±1.21% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,20 | 1 | 10 | 12.554mb | 0.080874s | ±2.86% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,20 | 1 | 10 | 12.473mb | 0.000007s | ±13.61% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,20 | 1 | 10 | 12.509mb | 0.004153s | ±2.27% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,20 | 1 | 10 | 12.509mb | 0.010935s | ±2.79% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,20 | 1 | 10 | 12.502mb | 0.000066s | ±9.52% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,20 | 1 | 10 | 12.502mb | 0.000066s | ±2.94% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,20 | 1 | 10 | 12.502mb | 0.000066s | ±2.51% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,20 | 1 | 10 | 12.798mb | 0.012576s | ±10.90% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,20 | 1 | 10 | 12.804mb | 0.012314s | ±11.26% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,20 | 1 | 10 | 12.563mb | 0.000429s | ±76.02% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,20 | 1 | 10 | 12.541mb | 0.000446s | ±2.50% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,20 | 1 | 10 | 12.558mb | 0.005883s | ±89.26% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,30 | 1 | 10 | 17.042mb | 0.010397s | ±3.89% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,30 | 1 | 10 | 17.046mb | 0.000104s | ±13.62% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,30 | 1 | 10 | 17.053mb | 0.000073s | ±5.49% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,30 | 1 | 10 | 17.047mb | 0.000107s | ±4.60% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,30 | 1 | 10 | 17.042mb | 0.010148s | ±3.27% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,30 | 1 | 10 | 17.084mb | 0.181320s | ±9.11% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,30 | 1 | 10 | 17.006mb | 0.000009s | ±4.55% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,30 | 1 | 10 | 17.042mb | 0.006459s | ±4.71% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,30 | 1 | 10 | 17.077mb | 0.017216s | ±5.29% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,30 | 1 | 10 | 17.046mb | 0.000107s | ±2.39% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,30 | 1 | 10 | 17.044mb | 0.000105s | ±3.36% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,30 | 1 | 10 | 17.046mb | 0.000106s | ±4.30% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,30 | 1 | 10 | 17.674mb | 0.095572s | ±6.65% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,30 | 1 | 10 | 17.691mb | 0.102611s | ±13.10% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,30 | 1 | 10 | 17.105mb | 0.001341s | ±36.46% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,30 | 1 | 10 | 17.104mb | 0.001306s | ±3.18% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,30 | 1 | 10 | 17.104mb | 0.006312s | ±51.42% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,40 | 1 | 10 | 23.131mb | 0.014433s | ±9.34% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,40 | 1 | 10 | 23.132mb | 0.000156s | ±218.86% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,40 | 1 | 10 | 23.173mb | 0.000108s | ±172.81% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,40 | 1 | 10 | 23.099mb | 0.000152s | ±4.32% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,40 | 1 | 10 | 23.086mb | 0.013914s | ±5.24% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,40 | 1 | 10 | 23.181mb | 0.319985s | ±5.15% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,40 | 1 | 10 | 23.044mb | 0.000007s | ±14.02% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,40 | 1 | 10 | 23.093mb | 0.008670s | ±5.04% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,40 | 1 | 10 | 23.095mb | 0.023546s | ±2.91% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,40 | 1 | 10 | 23.090mb | 0.000151s | ±2.76% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,40 | 1 | 10 | 23.089mb | 0.000148s | ±11.23% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,40 | 1 | 10 | 23.157mb | 0.000149s | ±142.96% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,40 | 1 | 10 | 24.233mb | 0.428470s | ±12.23% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,40 | 1 | 10 | 24.192mb | 0.386814s | ±7.69% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,40 | 1 | 10 | 23.277mb | 0.002867s | ±2.14% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,40 | 1 | 10 | 23.269mb | 0.002939s | ±1.61% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,40 | 1 | 10 | 23.276mb | 0.006179s | ±47.41% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,50 | 1 | 10 | 27.650mb | 0.018801s | ±4.38% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,50 | 1 | 10 | 27.663mb | 0.000208s | ±11.64% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,50 | 1 | 10 | 27.723mb | 0.000131s | ±12.28% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,50 | 1 | 10 | 27.667mb | 0.000210s | ±2.78% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,50 | 1 | 10 | 27.650mb | 0.018225s | ±2.90% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,50 | 1 | 10 | 27.787mb | 0.485798s | ±5.60% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,50 | 1 | 10 | 27.600mb | 0.000009s | ±11.13% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,50 | 1 | 10 | 27.630mb | 0.011091s | ±0.92% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,50 | 1 | 10 | 27.630mb | 0.030712s | ±0.76% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,50 | 1 | 10 | 27.652mb | 0.000206s | ±4.21% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,50 | 1 | 10 | 27.659mb | 0.000204s | ±13.61% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,50 | 1 | 10 | 27.652mb | 0.000207s | ±5.47% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,50 | 1 | 10 | 29.310mb | 1.206939s | ±10.50% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,50 | 1 | 10 | 29.390mb | 1.320606s | ±14.78% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,50 | 1 | 10 | 27.886mb | 0.005423s | ±3.75% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,50 | 1 | 10 | 27.885mb | 0.005669s | ±2.27% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,50 | 1 | 10 | 27.887mb | 0.011125s | ±3.40% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,60 | 1 | 10 | 32.214mb | 0.023670s | ±0.80% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,60 | 1 | 10 | 32.231mb | 0.000278s | ±2.58% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,60 | 1 | 10 | 32.300mb | 0.000165s | ±3.40% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,60 | 1 | 10 | 32.234mb | 0.000280s | ±1.53% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,60 | 1 | 10 | 32.214mb | 0.022865s | ±0.68% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,60 | 1 | 10 | 32.376mb | 0.686883s | ±6.15% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,60 | 1 | 10 | 32.156mb | 0.000008s | ±13.22% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,60 | 1 | 10 | 32.185mb | 0.013784s | ±1.14% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,60 | 1 | 10 | 32.201mb | 0.038390s | ±3.17% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,60 | 1 | 10 | 32.222mb | 0.000281s | ±10.97% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,60 | 1 | 10 | 32.223mb | 0.000271s | ±2.46% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,60 | 1 | 10 | 32.220mb | 0.000277s | ±12.89% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,60 | 1 | 10 | 34.723mb | 3.552521s | ±13.97% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,60 | 1 | 10 | 34.738mb | 3.476462s | ±14.63% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,60 | 1 | 10 | 32.496mb | 0.009344s | ±1.57% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,60 | 1 | 10 | 32.498mb | 0.009387s | ±3.33% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,60 | 1 | 10 | 32.495mb | 0.019263s | ±6.10% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,70 | 1 | 10 | 44.438mb | 0.029599s | ±3.34% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,70 | 1 | 10 | 44.433mb | 0.000359s | ±12.38% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,70 | 1 | 10 | 44.726mb | 0.000208s | ±3.93% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,70 | 1 | 10 | 44.461mb | 0.000365s | ±11.99% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,70 | 1 | 10 | 44.438mb | 0.028667s | ±0.84% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,70 | 1 | 10 | 44.794mb | 0.997542s | ±6.81% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,70 | 1 | 10 | 44.360mb | 0.000008s | ±14.31% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,70 | 1 | 10 | 44.391mb | 0.017181s | ±0.65% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,70 | 1 | 10 | 44.391mb | 0.048083s | ±0.67% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,70 | 1 | 10 | 44.420mb | 0.000351s | ±12.13% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,70 | 1 | 10 | 44.421mb | 0.000351s | ±5.41% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,70 | 1 | 10 | 44.420mb | 0.000351s | ±3.01% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,70 | 1 | 10 | 47.741mb | 8.258612s | ±11.24% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,70 | 1 | 10 | 47.702mb | 7.574171s | ±14.00% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,70 | 1 | 10 | 45.529mb | 0.015059s | ±1.48% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,70 | 1 | 10 | 45.533mb | 0.015142s | ±2.79% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,70 | 1 | 10 | 45.531mb | 0.030803s | ±7.93% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,80 | 1 | 10 | 49.284mb | 0.035633s | ±7.12% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,80 | 1 | 10 | 49.304mb | 0.000453s | ±0.95% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,80 | 1 | 10 | 49.635mb | 0.000254s | ±12.19% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,80 | 1 | 10 | 49.310mb | 0.000472s | ±3.59% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,80 | 1 | 10 | 49.284mb | 0.035766s | ±1.52% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,80 | 1 | 10 | 49.724mb | 1.294201s | ±7.97% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,80 | 1 | 10 | 49.197mb | 0.000007s | ±13.42% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,80 | 1 | 10 | 49.232mb | 0.020609s | ±1.12% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,80 | 1 | 10 | 49.232mb | 0.058361s | ±3.16% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,80 | 1 | 10 | 49.266mb | 0.000440s | ±1.60% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,80 | 1 | 10 | 49.266mb | 0.000444s | ±4.79% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,80 | 1 | 10 | 49.266mb | 0.000443s | ±10.14% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,80 | 1 | 10 | 53.461mb | 16.095014s | ±15.87% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,80 | 1 | 10 | 53.590mb | 17.480178s | ±15.05% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,80 | 1 | 10 | 50.531mb | 0.022074s | ±1.95% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,80 | 1 | 10 | 50.534mb | 0.022177s | ±2.54% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,80 | 1 | 10 | 50.533mb | 0.045875s | ±1.76% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,90 | 1 | 10 | 54.129mb | 0.042823s | ±0.75% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,90 | 1 | 10 | 54.151mb | 0.000553s | ±6.90% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,90 | 1 | 10 | 54.500mb | 0.000302s | ±2.45% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,90 | 1 | 10 | 54.159mb | 0.000565s | ±2.31% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,90 | 1 | 10 | 54.129mb | 0.041830s | ±1.53% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,90 | 1 | 10 | 54.518mb | 1.524077s | ±9.39% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,90 | 1 | 10 | 54.035mb | 0.000009s | ±15.11% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,90 | 1 | 10 | 54.073mb | 0.024268s | ±3.10% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,90 | 1 | 10 | 54.073mb | 0.068611s | ±0.86% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,90 | 1 | 10 | 54.110mb | 0.000534s | ±1.59% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,90 | 1 | 10 | 54.108mb | 0.000534s | ±2.85% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,90 | 1 | 10 | 54.109mb | 0.000536s | ±1.37% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,90 | 1 | 10 | 59.332mb | 28.202594s | ±14.13% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,90 | 1 | 10 | 59.359mb | 30.213354s | ±13.33% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,90 | 1 | 10 | 55.538mb | 0.030684s | ±5.05% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,90 | 1 | 10 | 55.536mb | 0.031059s | ±2.32% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,90 | 1 | 10 | 55.535mb | 0.065110s | ±2.80% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,100 | 1 | 10 | 58.975mb | 0.049534s | ±1.17% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,100 | 1 | 10 | 59.000mb | 0.000659s | ±0.98% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,100 | 1 | 10 | 59.411mb | 0.000359s | ±1.42% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,100 | 1 | 10 | 59.008mb | 0.000672s | ±2.88% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,100 | 1 | 10 | 58.975mb | 0.048570s | ±0.47% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,100 | 1 | 10 | 59.498mb | 1.692495s | ±8.43% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,100 | 1 | 10 | 58.872mb | 0.000007s | ±14.79% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,100 | 1 | 10 | 58.914mb | 0.027877s | ±1.41% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,100 | 1 | 10 | 58.914mb | 0.078369s | ±4.47% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,100 | 1 | 10 | 58.950mb | 0.000642s | ±25.20% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,100 | 1 | 10 | 58.954mb | 0.000644s | ±4.31% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,100 | 1 | 10 | 58.951mb | 0.000633s | ±3.12% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,100 | 1 | 10 | 65.485mb | 51.116404s | ±11.11% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,100 | 1 | 10 | 65.473mb | 46.500300s | ±12.30% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,100 | 1 | 10 | 60.536mb | 0.041783s | ±4.37% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,100 | 1 | 10 | 60.541mb | 0.043380s | ±1.90% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,100 | 1 | 10 | 60.537mb | 0.089583s | ±5.19% | ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ + +### Proportional + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++--------------------------+-------------------+-------------+------+-----+-----------+-------------+---------+ +| benchmark | subject | set | revs | its | mem_peak | mode | rstdev | ++--------------------------+-------------------+-------------+------+-----+-----------+-------------+---------+ +| MethodsProportionalBench | benchByCandidates | STV,3 | 1 | 10 | 5.827mb | 0.002783s | ±7.72% | +| MethodsProportionalBench | benchByCandidates | CPO STV,3 | 1 | 10 | 5.950mb | 0.006918s | ±90.37% | +| MethodsProportionalBench | benchByCandidates | STV,4 | 1 | 10 | 6.610mb | 0.003713s | ±8.19% | +| MethodsProportionalBench | benchByCandidates | CPO STV,4 | 1 | 10 | 6.754mb | 0.011379s | ±20.24% | +| MethodsProportionalBench | benchByCandidates | STV,5 | 1 | 10 | 7.402mb | 0.004458s | ±2.57% | +| MethodsProportionalBench | benchByCandidates | CPO STV,5 | 1 | 10 | 7.678mb | 0.019062s | ±19.13% | +| MethodsProportionalBench | benchByCandidates | STV,6 | 1 | 10 | 8.224mb | 0.004669s | ±8.40% | +| MethodsProportionalBench | benchByCandidates | CPO STV,6 | 1 | 10 | 10.395mb | 0.160481s | ±1.31% | +| MethodsProportionalBench | benchByCandidates | STV,7 | 1 | 10 | 9.045mb | 0.005661s | ±18.25% | +| MethodsProportionalBench | benchByCandidates | CPO STV,7 | 1 | 10 | 13.321mb | 0.332263s | ±1.08% | +| MethodsProportionalBench | benchByCandidates | STV,8 | 1 | 10 | 10.505mb | 0.007117s | ±9.94% | +| MethodsProportionalBench | benchByCandidates | CPO STV,8 | 1 | 10 | 18.022mb | 0.614972s | ±0.90% | +| MethodsProportionalBench | benchByCandidates | STV,9 | 1 | 10 | 11.342mb | 0.007548s | ±4.08% | +| MethodsProportionalBench | benchByCandidates | CPO STV,9 | 1 | 10 | 84.893mb | 5.877915s | ±0.57% | +| MethodsProportionalBench | benchByCandidates | STV,10 | 1 | 10 | 12.165mb | 0.008954s | ±13.21% | +| MethodsProportionalBench | benchByCandidates | CPO STV,10 | 1 | 10 | 155.658mb | 12.608246s | ±0.61% | +| MethodsProportionalBench | benchByCandidates | STV,11 | 1 | 10 | 12.988mb | 0.009892s | ±12.51% | +| MethodsProportionalBench | benchByCandidates | CPO STV,11 | 1 | 10 | 290.940mb | 25.131801s | ±0.51% | +| MethodsProportionalBench | benchByCandidates | STV,12 | 1 | 10 | 13.811mb | 0.009862s | ±4.28% | +| MethodsProportionalBench | benchByCandidates | CPO STV,12 | 1 | 10 | 2.417gb | 288.783020s | ±1.93% | +| MethodsProportionalBench | benchByCandidates | STV,13 | 1 | 10 | 14.632mb | 0.010369s | ±1.90% | +| MethodsProportionalBench | benchByCandidates | CPO STV,13 | 1 | 10 | 8.642mb | 0.000008s | ±9.13% | +| MethodsProportionalBench | benchByCandidates | STV,14 | 1 | 10 | 15.455mb | 0.011096s | ±15.92% | +| MethodsProportionalBench | benchByCandidates | CPO STV,14 | 1 | 10 | 9.088mb | 0.000008s | ±6.82% | +| MethodsProportionalBench | benchByCandidates | STV,15 | 1 | 10 | 16.278mb | 0.011425s | ±1.81% | +| MethodsProportionalBench | benchByCandidates | CPO STV,15 | 1 | 10 | 9.534mb | 0.000008s | ±11.18% | +| MethodsProportionalBench | benchByCandidates | STV,16 | 1 | 10 | 18.380mb | 0.012343s | ±2.40% | +| MethodsProportionalBench | benchByCandidates | CPO STV,16 | 1 | 10 | 10.621mb | 0.000009s | ±10.91% | +| MethodsProportionalBench | benchByCandidates | STV,17 | 1 | 10 | 19.260mb | 0.013621s | ±7.09% | +| MethodsProportionalBench | benchByCandidates | CPO STV,17 | 1 | 10 | 11.123mb | 0.000008s | ±7.91% | +| MethodsProportionalBench | benchByCandidates | STV,18 | 1 | 10 | 20.088mb | 0.013799s | ±2.16% | +| MethodsProportionalBench | benchByCandidates | CPO STV,18 | 1 | 10 | 11.575mb | 0.000007s | ±11.94% | +| MethodsProportionalBench | benchByCandidates | STV,19 | 1 | 10 | 20.916mb | 0.014612s | ±0.75% | +| MethodsProportionalBench | benchByCandidates | CPO STV,19 | 1 | 10 | 12.024mb | 0.000009s | ±5.27% | +| MethodsProportionalBench | benchByCandidates | STV,20 | 1 | 10 | 21.743mb | 0.015802s | ±3.77% | +| MethodsProportionalBench | benchByCandidates | CPO STV,20 | 1 | 10 | 12.473mb | 0.000008s | ±10.84% | +| MethodsProportionalBench | benchByCandidates | STV,30 | 1 | 10 | 30.044mb | 0.023349s | ±3.99% | +| MethodsProportionalBench | benchByCandidates | CPO STV,30 | 1 | 10 | 17.006mb | 0.000009s | ±10.26% | +| MethodsProportionalBench | benchByCandidates | STV,40 | 1 | 10 | 41.181mb | 0.032300s | ±4.33% | +| MethodsProportionalBench | benchByCandidates | CPO STV,40 | 1 | 10 | 23.044mb | 0.000009s | ±10.37% | +| MethodsProportionalBench | benchByCandidates | STV,50 | 1 | 10 | 49.535mb | 0.040636s | ±4.98% | +| MethodsProportionalBench | benchByCandidates | CPO STV,50 | 1 | 10 | 27.600mb | 0.000008s | ±7.71% | +| MethodsProportionalBench | benchByCandidates | STV,60 | 1 | 10 | 57.888mb | 0.051150s | ±2.49% | +| MethodsProportionalBench | benchByCandidates | CPO STV,60 | 1 | 10 | 32.156mb | 0.000009s | ±10.91% | +| MethodsProportionalBench | benchByCandidates | STV,70 | 1 | 10 | 79.741mb | 0.062366s | ±2.51% | +| MethodsProportionalBench | benchByCandidates | CPO STV,70 | 1 | 10 | 44.360mb | 0.000009s | ±13.15% | +| MethodsProportionalBench | benchByCandidates | STV,80 | 1 | 10 | 88.468mb | 0.074088s | ±3.38% | +| MethodsProportionalBench | benchByCandidates | CPO STV,80 | 1 | 10 | 49.197mb | 0.000008s | ±8.98% | +| MethodsProportionalBench | benchByCandidates | STV,90 | 1 | 10 | 97.212mb | 0.083958s | ±4.51% | +| MethodsProportionalBench | benchByCandidates | CPO STV,90 | 1 | 10 | 54.035mb | 0.000010s | ±12.74% | +| MethodsProportionalBench | benchByCandidates | STV,100 | 1 | 10 | 105.939mb | 0.097074s | ±3.65% | +| MethodsProportionalBench | benchByCandidates | CPO STV,100 | 1 | 10 | 58.872mb | 0.000009s | ±16.69% | ++--------------------------+-------------------+-------------+------+-----+-----------+-------------+---------+ + + +# v3.3.3 + +1000 random votes different for each test, variable number of candidates (look at column "set") + +* AMD Ryzen 9 5900X + +PHPBench (1.2.5) +with PHP version 8.1.6, xdebug ❌, opcache ✔ (with JIT Tracing) + +```php + #[Bench\Warmup(1)] + #[Bench\Iterations(3)] + #[Bench\Revs(1)] +``` + +### Non-Proportional + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ +| benchmark | subject | set | revs | its | mem_peak | mode | rstdev | ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ +| MethodsNonProportionalBench | benchByCandidates | BordaCount,3 | 1 | 3 | 4.155mb | 0.001963s | ±26.64% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,3 | 1 | 3 | 3.821mb | 0.000021s | ±5.85% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,3 | 1 | 3 | 3.749mb | 0.000019s | ±2.44% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,3 | 1 | 3 | 3.750mb | 0.000022s | ±5.58% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,3 | 1 | 3 | 3.811mb | 0.001702s | ±9.12% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,3 | 1 | 3 | 3.819mb | 0.005508s | ±50.37% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,3 | 1 | 3 | 3.815mb | 0.001621s | ±75.46% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,3 | 1 | 3 | 3.820mb | 0.001854s | ±10.30% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,3 | 1 | 3 | 3.818mb | 0.003639s | ±31.01% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,3 | 1 | 3 | 3.750mb | 0.000024s | ±5.27% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,3 | 1 | 3 | 3.750mb | 0.000024s | ±4.04% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,3 | 1 | 3 | 3.750mb | 0.000022s | ±4.16% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,3 | 1 | 3 | 3.819mb | 0.001493s | ±78.21% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,3 | 1 | 3 | 3.814mb | 0.000117s | ±131.05% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,3 | 1 | 3 | 3.813mb | 0.002645s | ±1.45% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,3 | 1 | 3 | 3.817mb | 0.001402s | ±3.32% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,3 | 1 | 3 | 3.815mb | 0.001413s | ±1.82% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,5 | 1 | 3 | 4.708mb | 0.003679s | ±20.48% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,5 | 1 | 3 | 4.641mb | 0.000029s | ±1.64% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,5 | 1 | 3 | 4.639mb | 0.000025s | ±3.87% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,5 | 1 | 3 | 4.641mb | 0.000029s | ±4.35% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,5 | 1 | 3 | 4.671mb | 0.002098s | ±0.18% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,5 | 1 | 3 | 4.702mb | 0.009091s | ±27.69% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,5 | 1 | 3 | 4.840mb | 0.000275s | ±2.76% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,5 | 1 | 3 | 4.702mb | 0.001902s | ±49.63% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,5 | 1 | 3 | 4.672mb | 0.004162s | ±0.18% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,5 | 1 | 3 | 4.641mb | 0.000061s | ±30.28% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,5 | 1 | 3 | 4.641mb | 0.000030s | ±4.11% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,5 | 1 | 3 | 4.641mb | 0.000030s | ±3.21% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,5 | 1 | 3 | 4.722mb | 0.003208s | ±101.35% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,5 | 1 | 3 | 4.662mb | 0.000069s | ±7.68% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,5 | 1 | 3 | 4.704mb | 0.005819s | ±49.15% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,5 | 1 | 3 | 4.701mb | 0.000128s | ±129.71% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,5 | 1 | 3 | 4.702mb | 0.016854s | ±43.28% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,6 | 1 | 3 | 5.115mb | 0.002719s | ±4.32% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,6 | 1 | 3 | 5.157mb | 0.001274s | ±88.64% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,6 | 1 | 3 | 5.146mb | 0.001564s | ±78.43% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,6 | 1 | 3 | 5.145mb | 0.000116s | ±133.45% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,6 | 1 | 3 | 5.115mb | 0.002493s | ±3.71% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,6 | 1 | 3 | 5.155mb | 0.012630s | ±14.09% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,6 | 1 | 3 | 5.892mb | 0.001688s | ±1.13% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,6 | 1 | 3 | 5.115mb | 0.001975s | ±5.80% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,6 | 1 | 3 | 5.116mb | 0.004745s | ±1.49% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,6 | 1 | 3 | 5.087mb | 0.000030s | ±3.21% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,6 | 1 | 3 | 5.087mb | 0.000030s | ±1.59% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,6 | 1 | 3 | 5.146mb | 0.000117s | ±132.89% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,6 | 1 | 3 | 5.183mb | 0.002663s | ±39.44% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,6 | 1 | 3 | 5.160mb | 0.002587s | ±67.19% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,6 | 1 | 3 | 5.155mb | 0.007516s | ±26.07% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,6 | 1 | 3 | 5.142mb | 0.000046s | ±8.09% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,6 | 1 | 3 | 5.145mb | 0.009898s | ±28.00% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,7 | 1 | 3 | 5.560mb | 0.002982s | ±6.98% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,7 | 1 | 3 | 5.532mb | 0.000030s | ±1.55% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,7 | 1 | 3 | 5.598mb | 0.000195s | ±137.37% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,7 | 1 | 3 | 5.594mb | 0.002681s | ±26.87% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,7 | 1 | 3 | 5.560mb | 0.002726s | ±4.55% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,7 | 1 | 3 | 5.597mb | 0.015668s | ±3.78% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,7 | 1 | 3 | 11.403mb | 0.014314s | ±0.84% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,7 | 1 | 3 | 5.560mb | 0.002040s | ±0.84% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,7 | 1 | 3 | 5.560mb | 0.005043s | ±0.56% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,7 | 1 | 3 | 5.594mb | 0.000115s | ±132.92% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,7 | 1 | 3 | 5.591mb | 0.000115s | ±132.92% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,7 | 1 | 3 | 5.590mb | 0.001316s | ±68.39% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,7 | 1 | 3 | 5.613mb | 0.001571s | ±30.65% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,7 | 1 | 3 | 5.570mb | 0.000148s | ±7.12% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,7 | 1 | 3 | 5.533mb | 0.000051s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,7 | 1 | 3 | 5.588mb | 0.000137s | ±126.51% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,7 | 1 | 3 | 5.590mb | 0.011652s | ±9.33% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,8 | 1 | 3 | 6.324mb | 0.003245s | ±2.60% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,8 | 1 | 3 | 6.351mb | 0.000121s | ±131.83% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,8 | 1 | 3 | 6.355mb | 0.002881s | ±36.70% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,8 | 1 | 3 | 6.299mb | 0.000030s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,8 | 1 | 3 | 6.324mb | 0.002904s | ±6.87% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,8 | 1 | 3 | 6.360mb | 0.019624s | ±1.95% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,8 | 1 | 3 | 86.609mb | 0.161120s | ±0.56% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,8 | 1 | 3 | 6.324mb | 0.002280s | ±0.32% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,8 | 1 | 3 | 6.324mb | 0.005577s | ±0.18% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,8 | 1 | 3 | 6.299mb | 0.000030s | ±3.07% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,8 | 1 | 3 | 6.299mb | 0.000030s | ±3.21% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,8 | 1 | 3 | 6.299mb | 0.000030s | ±3.07% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,8 | 1 | 3 | 6.348mb | 0.000191s | ±10.55% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,8 | 1 | 3 | 6.352mb | 0.000243s | ±7.51% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,8 | 1 | 3 | 6.300mb | 0.000063s | ±2.93% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,8 | 1 | 3 | 6.353mb | 0.000152s | ±122.80% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,8 | 1 | 3 | 6.353mb | 0.001166s | ±87.98% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,9 | 1 | 3 | 6.783mb | 0.003559s | ±2.27% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,9 | 1 | 3 | 6.759mb | 0.000034s | ±4.80% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,9 | 1 | 3 | 6.757mb | 0.000029s | ±9.12% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,9 | 1 | 3 | 6.759mb | 0.000033s | ±3.82% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,9 | 1 | 3 | 6.783mb | 0.003158s | ±0.32% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,9 | 1 | 3 | 6.813mb | 0.023297s | ±3.77% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,9 | 1 | 3 | 684.621mb | 1.772223s | ±0.04% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,9 | 1 | 3 | 6.783mb | 0.002401s | ±0.14% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,9 | 1 | 3 | 6.783mb | 0.006061s | ±1.52% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,9 | 1 | 3 | 6.759mb | 0.000036s | ±4.04% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,9 | 1 | 3 | 6.759mb | 0.000036s | ±7.03% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,9 | 1 | 3 | 6.759mb | 0.000034s | ±3.63% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,9 | 1 | 3 | 6.836mb | 0.000386s | ±88.55% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,9 | 1 | 3 | 6.828mb | 0.000291s | ±17.69% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,9 | 1 | 3 | 6.762mb | 0.000080s | ±10.50% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,9 | 1 | 3 | 6.762mb | 0.000087s | ±2.76% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,9 | 1 | 3 | 6.815mb | 0.003755s | ±69.19% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,10 | 1 | 3 | 7.230mb | 0.003945s | ±2.48% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,10 | 1 | 3 | 7.208mb | 0.000036s | ±2.67% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,10 | 1 | 3 | 7.206mb | 0.000030s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,10 | 1 | 3 | 7.208mb | 0.000036s | ±2.27% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,10 | 1 | 3 | 7.230mb | 0.003460s | ±1.93% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,10 | 1 | 3 | 7.238mb | 0.025816s | ±0.86% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,10 | 1 | 3 | 7.256mb | 0.000006s | ±35.79% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,10 | 1 | 3 | 7.230mb | 0.002606s | ±2.04% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,10 | 1 | 3 | 7.230mb | 0.006761s | ±1.65% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,10 | 1 | 3 | 7.208mb | 0.000038s | ±3.31% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,10 | 1 | 3 | 7.208mb | 0.000036s | ±2.67% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,10 | 1 | 3 | 7.208mb | 0.000038s | ±3.31% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,10 | 1 | 3 | 7.290mb | 0.000477s | ±8.17% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,10 | 1 | 3 | 7.293mb | 0.000578s | ±6.35% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,10 | 1 | 3 | 7.215mb | 0.000095s | ±0.99% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,10 | 1 | 3 | 7.215mb | 0.000101s | ±1.24% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,10 | 1 | 3 | 7.263mb | 0.000234s | ±111.07% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,11 | 1 | 3 | 7.676mb | 0.004294s | ±0.68% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,11 | 1 | 3 | 7.654mb | 0.000039s | ±5.22% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,11 | 1 | 3 | 7.653mb | 0.000032s | ±4.56% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,11 | 1 | 3 | 7.655mb | 0.000036s | ±6.26% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,11 | 1 | 3 | 7.676mb | 0.004028s | ±3.51% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,11 | 1 | 3 | 7.683mb | 0.029958s | ±4.71% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,11 | 1 | 3 | 7.639mb | 0.000005s | ±8.84% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,11 | 1 | 3 | 7.676mb | 0.002762s | ±6.54% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,11 | 1 | 3 | 7.676mb | 0.007102s | ±0.87% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,11 | 1 | 3 | 7.655mb | 0.000040s | ±2.40% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,11 | 1 | 3 | 7.655mb | 0.000038s | ±1.23% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,11 | 1 | 3 | 7.655mb | 0.000039s | ±2.38% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,11 | 1 | 3 | 7.751mb | 0.000748s | ±3.51% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,11 | 1 | 3 | 7.745mb | 0.000702s | ±20.15% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,11 | 1 | 3 | 7.708mb | 0.000205s | ±112.79% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,11 | 1 | 3 | 7.663mb | 0.000130s | ±4.27% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,11 | 1 | 3 | 7.709mb | 0.000356s | ±116.94% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,20 | 1 | 3 | 12.464mb | 0.008312s | ±7.15% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,20 | 1 | 3 | 12.464mb | 0.000229s | ±131.77% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,20 | 1 | 3 | 12.411mb | 0.000210s | ±133.66% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,20 | 1 | 3 | 12.406mb | 0.000061s | ±2.03% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,20 | 1 | 3 | 12.413mb | 0.006806s | ±0.73% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,20 | 1 | 3 | 12.456mb | 0.091459s | ±1.23% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,20 | 1 | 3 | 12.388mb | 0.000005s | ±23.57% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,20 | 1 | 3 | 12.453mb | 0.004731s | ±16.00% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,20 | 1 | 3 | 12.413mb | 0.012274s | ±2.14% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,20 | 1 | 3 | 12.406mb | 0.000068s | ±4.29% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,20 | 1 | 3 | 12.406mb | 0.000066s | ±2.11% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,20 | 1 | 3 | 12.406mb | 0.000065s | ±1.91% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,20 | 1 | 3 | 12.702mb | 0.013209s | ±12.23% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,20 | 1 | 3 | 12.692mb | 0.011353s | ±25.71% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,20 | 1 | 3 | 12.464mb | 0.000573s | ±68.31% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,20 | 1 | 3 | 12.445mb | 0.000496s | ±0.59% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,20 | 1 | 3 | 12.444mb | 0.000774s | ±0.82% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,30 | 1 | 3 | 16.946mb | 0.011932s | ±1.26% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,30 | 1 | 3 | 16.966mb | 0.000191s | ±116.41% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,30 | 1 | 3 | 16.956mb | 0.000074s | ±9.30% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,30 | 1 | 3 | 16.950mb | 0.000107s | ±2.78% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,30 | 1 | 3 | 16.946mb | 0.010802s | ±1.99% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,30 | 1 | 3 | 17.002mb | 0.190024s | ±1.38% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,30 | 1 | 3 | 16.928mb | 0.000008s | ±14.97% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,30 | 1 | 3 | 16.990mb | 0.008383s | ±20.97% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,30 | 1 | 3 | 16.981mb | 0.018721s | ±15.29% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,30 | 1 | 3 | 17.014mb | 0.000273s | ±125.55% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,30 | 1 | 3 | 16.948mb | 0.000103s | ±13.59% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,30 | 1 | 3 | 16.948mb | 0.000105s | ±3.52% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,30 | 1 | 3 | 17.577mb | 0.090871s | ±11.64% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,30 | 1 | 3 | 17.571mb | 0.100091s | ±6.46% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,30 | 1 | 3 | 17.007mb | 0.001403s | ±1.58% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,30 | 1 | 3 | 17.008mb | 0.001483s | ±1.41% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,30 | 1 | 3 | 17.006mb | 0.002474s | ±22.77% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,40 | 1 | 3 | 23.032mb | 0.017442s | ±16.43% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,40 | 1 | 3 | 23.043mb | 0.000399s | ±127.17% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,40 | 1 | 3 | 23.070mb | 0.000351s | ±132.04% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,40 | 1 | 3 | 23.003mb | 0.000159s | ±3.21% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,40 | 1 | 3 | 22.990mb | 0.014845s | ±0.38% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,40 | 1 | 3 | 23.066mb | 0.328225s | ±0.95% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,40 | 1 | 3 | 22.981mb | 0.000006s | ±7.44% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,40 | 1 | 3 | 22.981mb | 0.009713s | ±0.70% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,40 | 1 | 3 | 22.999mb | 0.026552s | ±3.05% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,40 | 1 | 3 | 22.991mb | 0.000150s | ±1.66% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,40 | 1 | 3 | 22.991mb | 0.000150s | ±1.92% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,40 | 1 | 3 | 22.993mb | 0.000153s | ±3.03% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,40 | 1 | 3 | 24.058mb | 0.416988s | ±8.69% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,40 | 1 | 3 | 24.113mb | 0.412503s | ±13.98% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,40 | 1 | 3 | 23.174mb | 0.003404s | ±2.64% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,40 | 1 | 3 | 23.173mb | 0.003211s | ±0.75% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,40 | 1 | 3 | 23.180mb | 0.005581s | ±2.42% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,50 | 1 | 3 | 27.569mb | 0.022380s | ±0.49% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,50 | 1 | 3 | 27.569mb | 0.000227s | ±4.03% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,50 | 1 | 3 | 27.646mb | 0.000231s | ±112.45% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,50 | 1 | 3 | 27.571mb | 0.000223s | ±3.56% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,50 | 1 | 3 | 27.569mb | 0.019937s | ±0.16% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,50 | 1 | 3 | 27.692mb | 0.517816s | ±9.10% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,50 | 1 | 3 | 27.569mb | 0.000007s | ±11.66% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,50 | 1 | 3 | 27.569mb | 0.012613s | ±1.68% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,50 | 1 | 3 | 27.569mb | 0.036053s | ±1.19% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,50 | 1 | 3 | 27.569mb | 0.000230s | ±2.40% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,50 | 1 | 3 | 27.569mb | 0.000212s | ±1.23% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,50 | 1 | 3 | 27.569mb | 0.000220s | ±9.60% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,50 | 1 | 3 | 29.229mb | 1.345662s | ±1.92% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,50 | 1 | 3 | 29.236mb | 1.587747s | ±5.93% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,50 | 1 | 3 | 27.786mb | 0.005877s | ±1.04% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,50 | 1 | 3 | 27.788mb | 0.006018s | ±3.38% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,50 | 1 | 3 | 27.788mb | 0.010559s | ±4.81% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,60 | 1 | 3 | 32.125mb | 0.028504s | ±0.93% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,60 | 1 | 3 | 32.133mb | 0.000283s | ±2.35% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,60 | 1 | 3 | 32.200mb | 0.000177s | ±6.66% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,60 | 1 | 3 | 32.138mb | 0.000298s | ±1.10% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,60 | 1 | 3 | 32.125mb | 0.027369s | ±1.79% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,60 | 1 | 3 | 32.258mb | 0.754619s | ±6.02% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,60 | 1 | 3 | 32.125mb | 0.000007s | ±11.66% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,60 | 1 | 3 | 32.125mb | 0.016028s | ±10.55% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,60 | 1 | 3 | 32.125mb | 0.044753s | ±5.16% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,60 | 1 | 3 | 32.125mb | 0.000287s | ±1.63% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,60 | 1 | 3 | 32.126mb | 0.000272s | ±2.14% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,60 | 1 | 3 | 32.125mb | 0.000278s | ±2.67% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,60 | 1 | 3 | 34.508mb | 3.316166s | ±11.62% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,60 | 1 | 3 | 34.505mb | 3.178537s | ±8.28% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,60 | 1 | 3 | 32.398mb | 0.010044s | ±1.64% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,60 | 1 | 3 | 32.399mb | 0.010514s | ±2.29% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,60 | 1 | 3 | 32.401mb | 0.018374s | ±1.32% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,70 | 1 | 3 | 44.389mb | 0.035290s | ±2.09% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,70 | 1 | 3 | 44.389mb | 0.000369s | ±1.94% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,70 | 1 | 3 | 44.618mb | 0.000225s | ±5.49% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,70 | 1 | 3 | 44.389mb | 0.000465s | ±81.93% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,70 | 1 | 3 | 44.389mb | 0.032422s | ±1.44% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,70 | 1 | 3 | 44.679mb | 1.037296s | ±10.52% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,70 | 1 | 3 | 44.389mb | 0.000007s | ±11.66% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,70 | 1 | 3 | 44.389mb | 0.019715s | ±1.45% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,70 | 1 | 3 | 44.389mb | 0.054181s | ±0.77% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,70 | 1 | 3 | 44.389mb | 0.000367s | ±0.78% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,70 | 1 | 3 | 44.389mb | 0.000355s | ±3.92% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,70 | 1 | 3 | 44.389mb | 0.000376s | ±2.39% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,70 | 1 | 3 | 47.592mb | 9.151059s | ±17.67% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,70 | 1 | 3 | 47.574mb | 7.939814s | ±10.66% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,70 | 1 | 3 | 45.430mb | 0.016193s | ±1.04% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,70 | 1 | 3 | 45.429mb | 0.016592s | ±1.85% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,70 | 1 | 3 | 45.433mb | 0.029352s | ±1.38% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,80 | 1 | 3 | 49.226mb | 0.044011s | ±0.15% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,80 | 1 | 3 | 49.226mb | 0.000477s | ±0.94% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,80 | 1 | 3 | 49.522mb | 0.000287s | ±3.17% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,80 | 1 | 3 | 49.226mb | 0.000470s | ±3.63% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,80 | 1 | 3 | 49.226mb | 0.040215s | ±0.08% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,80 | 1 | 3 | 49.544mb | 1.422216s | ±7.65% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,80 | 1 | 3 | 49.226mb | 0.000007s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,80 | 1 | 3 | 49.226mb | 0.024139s | ±0.52% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,80 | 1 | 3 | 49.226mb | 0.069787s | ±7.07% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,80 | 1 | 3 | 49.226mb | 0.000468s | ±0.46% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,80 | 1 | 3 | 49.226mb | 0.000468s | ±0.20% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,80 | 1 | 3 | 49.226mb | 0.000473s | ±2.93% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,80 | 1 | 3 | 53.379mb | 17.652835s | ±19.68% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,80 | 1 | 3 | 53.414mb | 16.093006s | ±12.74% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,80 | 1 | 3 | 50.432mb | 0.024409s | ±3.25% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,80 | 1 | 3 | 50.433mb | 0.024826s | ±1.25% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,80 | 1 | 3 | 50.437mb | 0.044515s | ±1.02% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,90 | 1 | 3 | 54.063mb | 0.051046s | ±5.67% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,90 | 1 | 3 | 54.063mb | 0.000782s | ±55.30% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,90 | 1 | 3 | 54.355mb | 0.000324s | ±0.77% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,90 | 1 | 3 | 54.063mb | 0.000590s | ±1.98% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,90 | 1 | 3 | 54.063mb | 0.048183s | ±2.90% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,90 | 1 | 3 | 54.455mb | 1.773768s | ±7.30% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,90 | 1 | 3 | 54.063mb | 0.000007s | ±7.07% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,90 | 1 | 3 | 54.063mb | 0.027222s | ±0.65% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,90 | 1 | 3 | 54.063mb | 0.076666s | ±1.09% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,90 | 1 | 3 | 54.063mb | 0.000551s | ±3.36% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,90 | 1 | 3 | 54.063mb | 0.000543s | ±1.06% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,90 | 1 | 3 | 54.063mb | 0.000540s | ±4.98% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,90 | 1 | 3 | 59.248mb | 30.973354s | ±8.26% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,90 | 1 | 3 | 59.257mb | 30.272496s | ±12.70% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,90 | 1 | 3 | 55.438mb | 0.034383s | ±1.00% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,90 | 1 | 3 | 55.440mb | 0.033415s | ±1.86% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,90 | 1 | 3 | 55.440mb | 0.060644s | ±2.29% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,100 | 1 | 3 | 59.031mb | 0.060027s | ±1.77% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,100 | 1 | 3 | 59.031mb | 0.000669s | ±2.31% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,100 | 1 | 3 | 59.329mb | 0.000383s | ±1.13% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,100 | 1 | 3 | 59.031mb | 0.000664s | ±1.62% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,100 | 1 | 3 | 59.031mb | 0.052348s | ±0.86% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,100 | 1 | 3 | 59.224mb | 1.946463s | ±4.10% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,100 | 1 | 3 | 59.031mb | 0.000007s | ±7.07% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,100 | 1 | 3 | 59.031mb | 0.032341s | ±1.92% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,100 | 1 | 3 | 59.031mb | 0.089906s | ±0.23% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,100 | 1 | 3 | 59.031mb | 0.000647s | ±2.31% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,100 | 1 | 3 | 59.031mb | 0.000649s | ±0.50% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,100 | 1 | 3 | 59.031mb | 0.000638s | ±0.37% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,100 | 1 | 3 | 65.349mb | 58.002654s | ±7.60% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,100 | 1 | 3 | 65.212mb | 50.089861s | ±4.77% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,100 | 1 | 3 | 60.436mb | 0.044041s | ±1.80% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,100 | 1 | 3 | 60.443mb | 0.044564s | ±1.62% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,100 | 1 | 3 | 60.442mb | 0.081513s | ±0.27% | ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ + +### Proportional + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++--------------------------+-------------------+---------+------+-----+----------+-----------+---------+ +| benchmark | subject | set | revs | its | mem_peak | mode | rstdev | ++--------------------------+-------------------+---------+------+-----+----------+-----------+---------+ +| MethodsProportionalBench | benchByCandidates | STV,3 | 1 | 3 | 3.849mb | 0.004075s | ±19.71% | +| MethodsProportionalBench | benchByCandidates | STV,4 | 1 | 3 | 4.228mb | 0.006325s | ±14.87% | +| MethodsProportionalBench | benchByCandidates | STV,5 | 1 | 3 | 4.672mb | 0.008955s | ±1.32% | +| MethodsProportionalBench | benchByCandidates | STV,6 | 1 | 3 | 5.119mb | 0.009425s | ±18.91% | +| MethodsProportionalBench | benchByCandidates | STV,7 | 1 | 3 | 5.563mb | 0.014790s | ±14.17% | +| MethodsProportionalBench | benchByCandidates | STV,8 | 1 | 3 | 6.327mb | 0.016129s | ±7.76% | +| MethodsProportionalBench | benchByCandidates | STV,9 | 1 | 3 | 6.788mb | 0.019538s | ±0.74% | +| MethodsProportionalBench | benchByCandidates | STV,10 | 1 | 3 | 7.235mb | 0.023839s | ±2.54% | +| MethodsProportionalBench | benchByCandidates | STV,11 | 1 | 3 | 7.682mb | 0.028752s | ±2.09% | +| MethodsProportionalBench | benchByCandidates | STV,12 | 1 | 3 | 8.129mb | 0.029920s | ±1.95% | +| MethodsProportionalBench | benchByCandidates | STV,13 | 1 | 3 | 8.575mb | 0.035721s | ±1.74% | +| MethodsProportionalBench | benchByCandidates | STV,14 | 1 | 3 | 9.022mb | 0.041041s | ±0.85% | +| MethodsProportionalBench | benchByCandidates | STV,15 | 1 | 3 | 9.467mb | 0.042278s | ±0.71% | +| MethodsProportionalBench | benchByCandidates | STV,16 | 1 | 3 | 10.570mb | 0.048256s | ±1.55% | +| MethodsProportionalBench | benchByCandidates | STV,17 | 1 | 3 | 11.073mb | 0.054690s | ±0.28% | +| MethodsProportionalBench | benchByCandidates | STV,18 | 1 | 3 | 11.529mb | 0.057881s | ±12.45% | +| MethodsProportionalBench | benchByCandidates | STV,19 | 1 | 3 | 11.977mb | 0.065760s | ±1.32% | +| MethodsProportionalBench | benchByCandidates | STV,20 | 1 | 3 | 12.428mb | 0.071830s | ±0.89% | +| MethodsProportionalBench | benchByCandidates | STV,30 | 1 | 3 | 16.967mb | 0.146342s | ±3.07% | +| MethodsProportionalBench | benchByCandidates | STV,40 | 1 | 3 | 23.056mb | 0.262451s | ±0.98% | +| MethodsProportionalBench | benchByCandidates | STV,50 | 1 | 3 | 27.666mb | 0.438554s | ±1.09% | +| MethodsProportionalBench | benchByCandidates | STV,60 | 1 | 3 | 32.266mb | 0.617602s | ±2.68% | +| MethodsProportionalBench | benchByCandidates | STV,70 | 1 | 3 | 44.724mb | 0.877067s | ±2.16% | +| MethodsProportionalBench | benchByCandidates | STV,80 | 1 | 3 | 49.657mb | 1.217623s | ±2.48% | +| MethodsProportionalBench | benchByCandidates | STV,90 | 1 | 3 | 54.599mb | 1.660393s | ±2.53% | +| MethodsProportionalBench | benchByCandidates | STV,100 | 1 | 3 | 59.679mb | 2.057089s | ±1.32% | ++--------------------------+-------------------+---------+------+-----+----------+-----------+---------+ + +# v3.2 Branch + +1000 random votes different for each test, variable number of candidates (look at column "set") + +* AMD Ryzen 9 5900X + +PHPBench (1.2.5) +with PHP version 8.1.6, xdebug ❌, opcache ✔ (with JIT Tracing) + +```php + #[Bench\Warmup(1)] + #[Bench\Iterations(3)] + #[Bench\Revs(1)] +``` + +### Non-Proportional + +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ +| benchmark | subject | set | revs | its | mem_peak | mode | rstdev | ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ +| MethodsNonProportionalBench | benchByCandidates | BordaCount,3 | 1 | 3 | 3.802mb | 0.001524s | ±11.42% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,3 | 1 | 3 | 3.732mb | 0.000022s | ±2.11% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,3 | 1 | 3 | 3.659mb | 0.000020s | ±2.32% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,3 | 1 | 3 | 3.660mb | 0.000022s | ±2.18% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,3 | 1 | 3 | 3.721mb | 0.001586s | ±3.03% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,3 | 1 | 3 | 3.729mb | 0.004477s | ±48.72% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,3 | 1 | 3 | 3.729mb | 0.000321s | ±133.35% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,3 | 1 | 3 | 3.725mb | 0.001545s | ±35.59% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,3 | 1 | 3 | 3.728mb | 0.003228s | ±20.81% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,3 | 1 | 3 | 3.660mb | 0.000023s | ±3.98% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,3 | 1 | 3 | 3.660mb | 0.000023s | ±2.08% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,3 | 1 | 3 | 3.660mb | 0.000023s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,3 | 1 | 3 | 3.728mb | 0.002659s | ±26.81% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,3 | 1 | 3 | 3.731mb | 0.003048s | ±74.21% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,3 | 1 | 3 | 3.661mb | 0.000029s | ±1.64% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,3 | 1 | 3 | 3.661mb | 0.000030s | ±1.59% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,3 | 1 | 3 | 3.661mb | 0.000030s | ±1.55% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,5 | 1 | 3 | 4.611mb | 0.002110s | ±27.68% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,5 | 1 | 3 | 4.551mb | 0.000031s | ±7.90% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,5 | 1 | 3 | 4.550mb | 0.000027s | ±8.31% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,5 | 1 | 3 | 4.551mb | 0.000030s | ±4.88% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,5 | 1 | 3 | 4.581mb | 0.002234s | ±33.60% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,5 | 1 | 3 | 4.612mb | 0.008804s | ±29.45% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,5 | 1 | 3 | 4.750mb | 0.000333s | ±8.48% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,5 | 1 | 3 | 4.612mb | 0.001945s | ±48.24% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,5 | 1 | 3 | 4.582mb | 0.004119s | ±26.82% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,5 | 1 | 3 | 4.551mb | 0.000030s | ±9.76% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,5 | 1 | 3 | 4.551mb | 0.000030s | ±3.07% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,5 | 1 | 3 | 4.551mb | 0.000030s | ±8.84% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,5 | 1 | 3 | 4.634mb | 0.001457s | ±119.79% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,5 | 1 | 3 | 4.570mb | 0.000072s | ±10.25% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,5 | 1 | 3 | 4.616mb | 0.007621s | ±75.79% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,5 | 1 | 3 | 4.611mb | 0.000134s | ±127.63% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,5 | 1 | 3 | 4.614mb | 0.019109s | ±18.08% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,6 | 1 | 3 | 5.025mb | 0.002424s | ±3.93% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,6 | 1 | 3 | 5.065mb | 0.001195s | ±95.20% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,6 | 1 | 3 | 5.057mb | 0.001578s | ±78.29% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,6 | 1 | 3 | 5.058mb | 0.000119s | ±133.48% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,6 | 1 | 3 | 5.026mb | 0.002764s | ±9.60% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,6 | 1 | 3 | 5.060mb | 0.011146s | ±0.61% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,6 | 1 | 3 | 5.803mb | 0.001708s | ±1.08% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,6 | 1 | 3 | 5.026mb | 0.001975s | ±2.18% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,6 | 1 | 3 | 5.057mb | 0.004808s | ±13.70% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,6 | 1 | 3 | 5.055mb | 0.000117s | ±133.46% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,6 | 1 | 3 | 4.997mb | 0.000031s | ±3.98% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,6 | 1 | 3 | 4.997mb | 0.000030s | ±14.21% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,6 | 1 | 3 | 5.090mb | 0.003927s | ±68.56% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,6 | 1 | 3 | 5.061mb | 0.001344s | ±63.57% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,6 | 1 | 3 | 5.065mb | 0.008249s | ±42.21% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,6 | 1 | 3 | 5.057mb | 0.000140s | ±127.64% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,6 | 1 | 3 | 5.056mb | 0.005456s | ±58.70% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,7 | 1 | 3 | 5.470mb | 0.002529s | ±0.86% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,7 | 1 | 3 | 5.505mb | 0.001398s | ±80.88% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,7 | 1 | 3 | 5.509mb | 0.000118s | ±133.57% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,7 | 1 | 3 | 5.511mb | 0.001358s | ±81.14% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,7 | 1 | 3 | 5.470mb | 0.002498s | ±1.33% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,7 | 1 | 3 | 5.510mb | 0.016885s | ±13.43% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,7 | 1 | 3 | 11.313mb | 0.014489s | ±0.44% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,7 | 1 | 3 | 5.470mb | 0.001977s | ±3.64% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,7 | 1 | 3 | 5.470mb | 0.004855s | ±0.99% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,7 | 1 | 3 | 5.501mb | 0.001342s | ±68.36% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,7 | 1 | 3 | 5.442mb | 0.000031s | ±2.98% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,7 | 1 | 3 | 5.442mb | 0.000033s | ±14.72% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,7 | 1 | 3 | 5.521mb | 0.001549s | ±34.02% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,7 | 1 | 3 | 5.519mb | 0.000237s | ±106.41% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,7 | 1 | 3 | 5.500mb | 0.000138s | ±126.56% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,7 | 1 | 3 | 5.500mb | 0.000139s | ±126.76% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,7 | 1 | 3 | 5.503mb | 0.023529s | ±47.80% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,8 | 1 | 3 | 6.234mb | 0.002813s | ±1.25% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,8 | 1 | 3 | 6.208mb | 0.000035s | ±2.75% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,8 | 1 | 3 | 6.271mb | 0.001368s | ±68.40% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,8 | 1 | 3 | 6.263mb | 0.000118s | ±132.31% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,8 | 1 | 3 | 6.234mb | 0.002856s | ±2.60% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,8 | 1 | 3 | 6.270mb | 0.018567s | ±3.23% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,8 | 1 | 3 | 86.520mb | 0.161483s | ±0.97% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,8 | 1 | 3 | 6.235mb | 0.002310s | ±0.80% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,8 | 1 | 3 | 6.235mb | 0.005669s | ±3.64% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,8 | 1 | 3 | 6.209mb | 0.000035s | ±4.95% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,8 | 1 | 3 | 6.263mb | 0.000134s | ±132.55% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,8 | 1 | 3 | 6.209mb | 0.000038s | ±15.96% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,8 | 1 | 3 | 6.260mb | 0.000224s | ±5.62% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,8 | 1 | 3 | 6.292mb | 0.000300s | ±97.60% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,8 | 1 | 3 | 6.210mb | 0.000065s | ±1.91% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,8 | 1 | 3 | 6.210mb | 0.000070s | ±0.68% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,8 | 1 | 3 | 6.265mb | 0.010512s | ±58.91% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,9 | 1 | 3 | 6.693mb | 0.003095s | ±0.95% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,9 | 1 | 3 | 6.669mb | 0.000037s | ±1.29% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,9 | 1 | 3 | 6.669mb | 0.000113s | ±132.80% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,9 | 1 | 3 | 6.669mb | 0.000036s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,9 | 1 | 3 | 6.737mb | 0.003168s | ±16.73% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,9 | 1 | 3 | 6.723mb | 0.020616s | ±9.56% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,9 | 1 | 3 | 684.597mb | 1.759478s | ±0.43% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,9 | 1 | 3 | 6.693mb | 0.002496s | ±3.81% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,9 | 1 | 3 | 6.693mb | 0.005934s | ±3.85% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,9 | 1 | 3 | 6.669mb | 0.000036s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,9 | 1 | 3 | 6.669mb | 0.000036s | ±4.54% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,9 | 1 | 3 | 6.669mb | 0.000036s | ±2.57% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,9 | 1 | 3 | 6.746mb | 0.000464s | ±80.49% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,9 | 1 | 3 | 6.754mb | 0.000386s | ±87.16% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,9 | 1 | 3 | 6.724mb | 0.000242s | ±130.20% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,9 | 1 | 3 | 6.672mb | 0.000081s | ±1.17% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,9 | 1 | 3 | 6.726mb | 0.015371s | ±29.12% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,10 | 1 | 3 | 7.149mb | 0.003372s | ±1.17% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,10 | 1 | 3 | 7.172mb | 0.000120s | ±131.10% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,10 | 1 | 3 | 7.116mb | 0.000031s | ±3.11% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,10 | 1 | 3 | 7.118mb | 0.000037s | ±6.00% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,10 | 1 | 3 | 7.149mb | 0.003277s | ±0.85% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,10 | 1 | 3 | 7.155mb | 0.024746s | ±1.43% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,10 | 1 | 3 | 7.166mb | 0.000005s | ±16.64% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,10 | 1 | 3 | 7.148mb | 0.002491s | ±0.34% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,10 | 1 | 3 | 7.148mb | 0.006318s | ±0.67% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,10 | 1 | 3 | 7.118mb | 0.000038s | ±3.31% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,10 | 1 | 3 | 7.118mb | 0.000037s | ±4.51% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,10 | 1 | 3 | 7.118mb | 0.000039s | ±13.05% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,10 | 1 | 3 | 7.199mb | 0.000637s | ±15.45% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,10 | 1 | 3 | 7.195mb | 0.000542s | ±25.69% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,10 | 1 | 3 | 7.125mb | 0.000096s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,10 | 1 | 3 | 7.125mb | 0.000102s | ±5.28% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,10 | 1 | 3 | 7.172mb | 0.000817s | ±135.94% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,11 | 1 | 3 | 7.594mb | 0.003904s | ±2.70% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,11 | 1 | 3 | 7.565mb | 0.000042s | ±1.13% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,11 | 1 | 3 | 7.564mb | 0.000036s | ±5.76% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,11 | 1 | 3 | 7.565mb | 0.000038s | ±3.63% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,11 | 1 | 3 | 7.594mb | 0.003622s | ±0.45% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,11 | 1 | 3 | 7.626mb | 0.028215s | ±8.72% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,11 | 1 | 3 | 7.549mb | 0.000006s | ±14.14% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,11 | 1 | 3 | 7.594mb | 0.002663s | ±0.38% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,11 | 1 | 3 | 7.594mb | 0.006780s | ±1.90% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,11 | 1 | 3 | 7.565mb | 0.000041s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,11 | 1 | 3 | 7.565mb | 0.000041s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,11 | 1 | 3 | 7.565mb | 0.000041s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,11 | 1 | 3 | 7.654mb | 0.000733s | ±7.38% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,11 | 1 | 3 | 7.660mb | 0.000896s | ±6.96% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,11 | 1 | 3 | 7.573mb | 0.000117s | ±2.54% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,11 | 1 | 3 | 7.573mb | 0.000122s | ±0.67% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,11 | 1 | 3 | 7.618mb | 0.000264s | ±106.00% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,20 | 1 | 3 | 12.366mb | 0.007824s | ±8.70% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,20 | 1 | 3 | 12.362mb | 0.001393s | ±65.94% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,20 | 1 | 3 | 12.312mb | 0.001167s | ±98.99% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,20 | 1 | 3 | 12.308mb | 0.000064s | ±14.33% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,20 | 1 | 3 | 12.315mb | 0.006407s | ±4.24% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,20 | 1 | 3 | 12.335mb | 0.081311s | ±2.61% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,20 | 1 | 3 | 12.289mb | 0.000006s | ±8.32% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,20 | 1 | 3 | 12.314mb | 0.004337s | ±2.07% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,20 | 1 | 3 | 12.314mb | 0.011260s | ±0.41% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,20 | 1 | 3 | 12.307mb | 0.000067s | ±14.51% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,20 | 1 | 3 | 12.306mb | 0.000064s | ±1.49% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,20 | 1 | 3 | 12.306mb | 0.000063s | ±2.21% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,20 | 1 | 3 | 12.596mb | 0.012358s | ±11.33% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,20 | 1 | 3 | 12.591mb | 0.013436s | ±10.97% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,20 | 1 | 3 | 12.370mb | 0.000541s | ±72.09% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,20 | 1 | 3 | 12.345mb | 0.000470s | ±1.76% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,20 | 1 | 3 | 12.362mb | 0.001062s | ±98.11% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,30 | 1 | 3 | 16.868mb | 0.010368s | ±12.04% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,30 | 1 | 3 | 16.874mb | 0.001211s | ±87.03% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,30 | 1 | 3 | 16.863mb | 0.000159s | ±122.81% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,30 | 1 | 3 | 16.811mb | 0.000105s | ±2.99% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,30 | 1 | 3 | 16.806mb | 0.009620s | ±1.73% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,30 | 1 | 3 | 16.872mb | 0.187050s | ±2.29% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,30 | 1 | 3 | 16.788mb | 0.000007s | ±17.01% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,30 | 1 | 3 | 16.805mb | 0.007006s | ±1.68% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,30 | 1 | 3 | 16.841mb | 0.021321s | ±7.10% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,30 | 1 | 3 | 16.807mb | 0.000101s | ±4.00% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,30 | 1 | 3 | 16.808mb | 0.000099s | ±4.57% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,30 | 1 | 3 | 16.807mb | 0.000107s | ±2.29% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,30 | 1 | 3 | 17.421mb | 0.093493s | ±5.52% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,30 | 1 | 3 | 17.443mb | 0.104010s | ±2.17% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,30 | 1 | 3 | 16.867mb | 0.001384s | ±5.49% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,30 | 1 | 3 | 16.868mb | 0.001406s | ±2.41% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,30 | 1 | 3 | 16.867mb | 0.002296s | ±2.15% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,40 | 1 | 3 | 22.913mb | 0.015279s | ±13.36% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,40 | 1 | 3 | 22.895mb | 0.000435s | ±128.97% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,40 | 1 | 3 | 22.936mb | 0.000189s | ±116.58% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,40 | 1 | 3 | 22.863mb | 0.000158s | ±2.90% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,40 | 1 | 3 | 22.848mb | 0.013507s | ±0.93% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,40 | 1 | 3 | 22.945mb | 0.291114s | ±10.19% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,40 | 1 | 3 | 22.841mb | 0.000006s | ±8.32% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,40 | 1 | 3 | 22.853mb | 0.009024s | ±13.62% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,40 | 1 | 3 | 22.857mb | 0.025075s | ±2.78% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,40 | 1 | 3 | 22.853mb | 0.000151s | ±1.96% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,40 | 1 | 3 | 22.853mb | 0.000151s | ±0.83% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,40 | 1 | 3 | 22.852mb | 0.000150s | ±0.83% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,40 | 1 | 3 | 23.967mb | 0.461000s | ±2.30% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,40 | 1 | 3 | 23.983mb | 0.467482s | ±6.05% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,40 | 1 | 3 | 23.039mb | 0.003041s | ±2.43% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,40 | 1 | 3 | 23.039mb | 0.003024s | ±2.47% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,40 | 1 | 3 | 23.040mb | 0.005450s | ±10.49% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,50 | 1 | 3 | 27.429mb | 0.019048s | ±0.77% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,50 | 1 | 3 | 27.429mb | 0.000205s | ±1.80% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,50 | 1 | 3 | 27.481mb | 0.000131s | ±1.57% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,50 | 1 | 3 | 27.429mb | 0.000211s | ±1.48% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,50 | 1 | 3 | 27.429mb | 0.018321s | ±1.88% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,50 | 1 | 3 | 27.533mb | 0.466429s | ±6.92% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,50 | 1 | 3 | 27.429mb | 0.000007s | ±0.00% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,50 | 1 | 3 | 27.429mb | 0.011725s | ±1.17% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,50 | 1 | 3 | 27.429mb | 0.031058s | ±0.39% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,50 | 1 | 3 | 27.429mb | 0.000201s | ±2.11% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,50 | 1 | 3 | 27.429mb | 0.000202s | ±1.62% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,50 | 1 | 3 | 27.429mb | 0.000209s | ±0.82% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,50 | 1 | 3 | 29.101mb | 1.375436s | ±8.57% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,50 | 1 | 3 | 29.062mb | 1.306662s | ±10.02% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,50 | 1 | 3 | 27.650mb | 0.005984s | ±1.00% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,50 | 1 | 3 | 27.646mb | 0.005817s | ±1.80% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,50 | 1 | 3 | 27.646mb | 0.010318s | ±16.23% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,60 | 1 | 3 | 31.984mb | 0.024316s | ±1.00% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,60 | 1 | 3 | 31.991mb | 0.000272s | ±1.20% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,60 | 1 | 3 | 32.058mb | 0.000170s | ±7.50% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,60 | 1 | 3 | 31.996mb | 0.000272s | ±1.30% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,60 | 1 | 3 | 31.984mb | 0.023471s | ±1.97% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,60 | 1 | 3 | 32.114mb | 0.725998s | ±3.91% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,60 | 1 | 3 | 31.984mb | 0.000005s | ±16.64% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,60 | 1 | 3 | 31.984mb | 0.014803s | ±0.94% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,60 | 1 | 3 | 31.984mb | 0.040944s | ±2.69% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,60 | 1 | 3 | 31.984mb | 0.000272s | ±0.91% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,60 | 1 | 3 | 31.984mb | 0.000272s | ±1.81% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,60 | 1 | 3 | 31.984mb | 0.000275s | ±1.73% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,60 | 1 | 3 | 34.517mb | 3.339907s | ±9.85% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,60 | 1 | 3 | 34.478mb | 3.625151s | ±7.60% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,60 | 1 | 3 | 32.255mb | 0.009959s | ±1.61% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,60 | 1 | 3 | 32.257mb | 0.009910s | ±1.81% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,60 | 1 | 3 | 32.256mb | 0.021020s | ±5.02% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,70 | 1 | 3 | 44.247mb | 0.030676s | ±0.91% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,70 | 1 | 3 | 44.247mb | 0.000349s | ±1.19% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,70 | 1 | 3 | 44.486mb | 0.000210s | ±3.75% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,70 | 1 | 3 | 44.247mb | 0.000356s | ±0.99% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,70 | 1 | 3 | 44.247mb | 0.029905s | ±0.71% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,70 | 1 | 3 | 44.481mb | 1.003590s | ±7.87% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,70 | 1 | 3 | 44.247mb | 0.000007s | ±7.07% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,70 | 1 | 3 | 44.247mb | 0.018283s | ±0.08% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,70 | 1 | 3 | 44.247mb | 0.050120s | ±0.75% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,70 | 1 | 3 | 44.247mb | 0.000348s | ±0.49% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,70 | 1 | 3 | 44.247mb | 0.000348s | ±1.20% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,70 | 1 | 3 | 44.247mb | 0.000355s | ±12.89% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,70 | 1 | 3 | 47.505mb | 7.590197s | ±16.35% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,70 | 1 | 3 | 47.410mb | 7.557485s | ±5.37% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,70 | 1 | 3 | 45.295mb | 0.015402s | ±2.51% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,70 | 1 | 3 | 45.291mb | 0.015401s | ±0.70% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,70 | 1 | 3 | 45.294mb | 0.028018s | ±0.32% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,80 | 1 | 3 | 49.084mb | 0.037037s | ±0.25% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,80 | 1 | 3 | 49.084mb | 0.000443s | ±0.11% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,80 | 1 | 3 | 49.370mb | 0.000257s | ±1.43% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,80 | 1 | 3 | 49.084mb | 0.000441s | ±2.97% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,80 | 1 | 3 | 49.084mb | 0.036051s | ±0.70% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,80 | 1 | 3 | 49.347mb | 1.215646s | ±3.64% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,80 | 1 | 3 | 49.084mb | 0.000007s | ±7.07% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,80 | 1 | 3 | 49.084mb | 0.021718s | ±0.57% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,80 | 1 | 3 | 49.084mb | 0.060641s | ±0.71% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,80 | 1 | 3 | 49.084mb | 0.000432s | ±0.85% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,80 | 1 | 3 | 49.084mb | 0.000435s | ±0.78% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,80 | 1 | 3 | 49.084mb | 0.000427s | ±0.83% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,80 | 1 | 3 | 53.274mb | 14.873456s | ±21.68% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,80 | 1 | 3 | 53.346mb | 15.733408s | ±16.64% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,80 | 1 | 3 | 50.295mb | 0.022662s | ±2.31% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,80 | 1 | 3 | 50.294mb | 0.023019s | ±3.50% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,80 | 1 | 3 | 50.292mb | 0.041399s | ±1.72% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,90 | 1 | 3 | 53.920mb | 0.044067s | ±1.03% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,90 | 1 | 3 | 53.920mb | 0.000540s | ±0.54% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,90 | 1 | 3 | 54.270mb | 0.000414s | ±82.76% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,90 | 1 | 3 | 53.920mb | 0.000546s | ±0.60% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,90 | 1 | 3 | 53.920mb | 0.042732s | ±0.28% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,90 | 1 | 3 | 54.186mb | 1.534839s | ±4.49% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,90 | 1 | 3 | 53.920mb | 0.000008s | ±5.66% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,90 | 1 | 3 | 53.920mb | 0.025321s | ±0.90% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,90 | 1 | 3 | 53.920mb | 0.072044s | ±0.53% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,90 | 1 | 3 | 53.920mb | 0.000530s | ±1.08% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,90 | 1 | 3 | 53.920mb | 0.000527s | ±0.09% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,90 | 1 | 3 | 53.920mb | 0.000540s | ±4.15% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,90 | 1 | 3 | 59.325mb | 28.542581s | ±14.47% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,90 | 1 | 3 | 59.069mb | 28.519612s | ±8.88% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,90 | 1 | 3 | 55.295mb | 0.032344s | ±1.29% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,90 | 1 | 3 | 55.296mb | 0.031541s | ±1.60% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,90 | 1 | 3 | 55.291mb | 0.057553s | ±0.56% | +| MethodsNonProportionalBench | benchByCandidates | BordaCount,100 | 1 | 3 | 58.888mb | 0.051550s | ±1.33% | +| MethodsNonProportionalBench | benchByCandidates | Copeland,100 | 1 | 3 | 58.888mb | 0.000641s | ±0.96% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Quick,100 | 1 | 3 | 59.152mb | 0.000353s | ±0.71% | +| MethodsNonProportionalBench | benchByCandidates | Dodgson Tideman Approximation,100 | 1 | 3 | 58.888mb | 0.000646s | ±1.07% | +| MethodsNonProportionalBench | benchByCandidates | DowdallSystem,100 | 1 | 3 | 58.888mb | 0.050364s | ±0.64% | +| MethodsNonProportionalBench | benchByCandidates | Instant-runoff,100 | 1 | 3 | 59.071mb | 1.853938s | ±3.54% | +| MethodsNonProportionalBench | benchByCandidates | Kemeny–Young,100 | 1 | 3 | 58.888mb | 0.000008s | ±16.27% | +| MethodsNonProportionalBench | benchByCandidates | First-past-the-post voting,100 | 1 | 3 | 58.888mb | 0.029426s | ±0.22% | +| MethodsNonProportionalBench | benchByCandidates | Multiple Rounds System,100 | 1 | 3 | 58.888mb | 0.084283s | ±0.29% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Winning,100 | 1 | 3 | 58.888mb | 0.000627s | ±0.98% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Margin,100 | 1 | 3 | 58.888mb | 0.000628s | ±0.75% | +| MethodsNonProportionalBench | benchByCandidates | Minimax Opposition,100 | 1 | 3 | 58.888mb | 0.000622s | ±0.66% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Margin,100 | 1 | 3 | 65.109mb | 43.207162s | ±10.17% | +| MethodsNonProportionalBench | benchByCandidates | Ranked Pairs Winning,100 | 1 | 3 | 65.087mb | 47.362454s | ±5.24% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Winning,100 | 1 | 3 | 60.295mb | 0.044037s | ±0.95% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Margin,100 | 1 | 3 | 60.298mb | 0.044785s | ±0.54% | +| MethodsNonProportionalBench | benchByCandidates | Schulze Ratio,100 | 1 | 3 | 60.297mb | 0.076305s | ±2.64% | ++-----------------------------+-------------------+-----------------------------------+------+-----+-----------+------------+----------+ \ No newline at end of file diff --git a/include/Condorcet/Benchmarks/History/WeakRef_v3.3.md b/include/Condorcet/Benchmarks/History/WeakRef_v3.3.md new file mode 100644 index 0000000..675347b --- /dev/null +++ b/include/Condorcet/Benchmarks/History/WeakRef_v3.3.md @@ -0,0 +1,61 @@ +# Conclusion +When the garbage collector is actively used, there is no significant difference (memory used). If not, the implementation of weak references is much more economic. + +# v3.2 Branch + +## Time Centric +``` +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++------+---------------------+----------------------+-----+------+-------------+---------------+--------------+----------------+ +| iter | benchmark | subject | set | revs | mem_peak | time_avg | comp_z_value | comp_deviation | ++------+---------------------+----------------------+-----+------+-------------+---------------+--------------+----------------+ +| 0 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 27,103,632b | 258,943.200μs | -0.10σ | -0.05% | +| 1 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 27,103,632b | 257,405.200μs | -1.24σ | -0.65% | +| 2 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 27,103,632b | 261,179.300μs | +1.55σ | +0.81% | +| 3 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 27,103,632b | 258,782.800μs | -0.22σ | -0.11% | ++------+---------------------+----------------------+-----+------+-------------+---------------+--------------+----------------+ + +``` + +## Memory Centric +``` +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++------+---------------------+----------------------+-----+------+------------+-----------------+--------------+----------------+ +| iter | benchmark | subject | set | revs | mem_peak | time_avg | comp_z_value | comp_deviation | ++------+---------------------+----------------------+-----+------+------------+-----------------+--------------+----------------+ +| 0 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 6,883,080b | 2,605,364.000μs | -0.60σ | -0.42% | +| 1 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 6,883,080b | 2,591,650.000μs | -1.33σ | -0.95% | +| 2 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 6,883,080b | 2,633,507.000μs | +0.92σ | +0.65% | +| 3 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 6,883,080b | 2,635,192.000μs | +1.01σ | +0.72% | ++------+---------------------+----------------------+-----+------+------------+-----------------+--------------+----------------+ + +``` + + +# v3.3 Branch + +## Time Centric +``` +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++------+---------------------+----------------------+-----+------+------------+---------------+--------------+----------------+ +| iter | benchmark | subject | set | revs | mem_peak | time_avg | comp_z_value | comp_deviation | ++------+---------------------+----------------------+-----+------+------------+---------------+--------------+----------------+ +| 0 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 7,045,952b | 279,167.000μs | -0.30σ | -0.12% | +| 1 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 7,045,952b | 281,010.000μs | +1.30σ | +0.54% | +| 2 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 7,045,952b | 280,016.000μs | +0.43σ | +0.18% | +| 3 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 7,045,952b | 277,863.500μs | -1.43σ | -0.59% | ++------+---------------------+----------------------+-----+------+------------+---------------+--------------+----------------+ +``` + +## Memory Centric +``` +Subjects: 1, Assertions: 0, Failures: 0, Errors: 0 ++------+---------------------+----------------------+-----+------+------------+-----------------+--------------+----------------+ +| iter | benchmark | subject | set | revs | mem_peak | time_avg | comp_z_value | comp_deviation | ++------+---------------------+----------------------+-----+------+------------+-----------------+--------------+----------------+ +| 0 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 7,041,560b | 2,847,455.000μs | +1.37σ | +0.80% | +| 1 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 7,041,560b | 2,801,896.000μs | -1.40σ | -0.81% | +| 2 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 7,041,560b | 2,829,893.000μs | +0.30σ | +0.18% | +| 3 | IntensiveUsageBench | benchSimpleManyVotes | | 10 | 7,041,560b | 2,820,355.000μs | -0.28σ | -0.16% | ++------+---------------------+----------------------+-----+------+------------+-----------------+--------------+----------------+ +``` \ No newline at end of file diff --git a/include/Condorcet/Benchmarks/Instructions.md b/include/Condorcet/Benchmarks/Instructions.md new file mode 100644 index 0000000..904c978 --- /dev/null +++ b/include/Condorcet/Benchmarks/Instructions.md @@ -0,0 +1,39 @@ +## Instructions + +``` composer require --dev phpbench/phpbench ``` + +### Simple Suite +#### Time Centric +``` ./vendor/bin/phpbench run Benchmarks/SimpleUsageBench.php --report=default ``` +#### Memory Centric +``` ./vendor/bin/phpbench run Benchmarks/SimpleUsageBench.php --report=default --executor=memory_centric_microtime ``` + + +### Intensive Suite +#### Time Centric +``` ./vendor/bin/phpbench run Benchmarks/IntensiveUsageBench.php --report=default ``` +#### Memory Centric +``` ./vendor/bin/phpbench run Benchmarks/IntensiveUsageBench.php --report=default --executor=memory_centric_microtime ``` + + +### Run Specifics developement benchmarks + +#### Pairwise Optimisation on Update (between commits) + +``` ./vendor/bin/phpbench run Benchmarks/PairwiseUpdateOptimizationBench.php --report=default --executor=memory_centric_microtime ``` + +#### Pairwse and addVote performance related to election number of candidates + +``` ./vendor/bin/phpbench run Benchmarks/PairwiseNumberOfCandidatesBench.php --report=default ``` + +#### Methods speed test by Candidates numbers + +``` ./vendor/bin/phpbench run Benchmarks/MethodsNonProportionalBench.php --report=aggregate ``` +``` ./vendor/bin/phpbench run Benchmarks/MethodsProportionalBench.php --report=aggregate ``` + +#### Add Votes (1000 votes with 100 candidates) +``` ./vendor/bin/phpbench run Benchmarks/AddVotesBench.php --report=default ``` + +#### Kemeny-Young Speed & Memory Test + +``` ./vendor/bin/phpbench run Benchmarks/KemenyYoungBench.php --report=default --executor=memory_centric_microtime ``` \ No newline at end of file diff --git a/include/Condorcet/Benchmarks/IntensiveUsageBench.php b/include/Condorcet/Benchmarks/IntensiveUsageBench.php new file mode 100644 index 0000000..8f88cb3 --- /dev/null +++ b/include/Condorcet/Benchmarks/IntensiveUsageBench.php @@ -0,0 +1,67 @@ +allowsVoteWeight(true); + $election->setNumberOfSeats(2); + + $election->parseCandidates('A;B;C;D;E;F'); + + $election->parseVotes(' + Ultimate Question of Life || A>B>C ^42 * 42 + C=A>B ^2 * 200 + B>C + E > B > C > A ^80 *50 + F > B > G > H > A* 250 + D = B = E > F ^6 * 48 + '); + + $election->getCondorcetWinner(); + $election->getCondorcetLoser(); + + foreach (Condorcet::getAuthMethods() as $method) { + $election->getResult($method); + } + + $election->setImplicitRanking(false); + + foreach (Condorcet::getAuthMethods() as $method) { + $election->getResult($method); + } + + $election->allowsVoteWeight(false); + + foreach (Condorcet::getAuthMethods() as $method) { + $election->getResult($method); + } + + $election->parseVotes(' + Ultimate Question of Life || C>B>A ^42 * 42 + C=A=B ^2 * 200 + B>C + A > C >E ^80 *50 + G > B > H > F* 250 + C = B = E > A ^6 * 48 + '); + + foreach (Condorcet::getAuthMethods() as $method) { + $election->getResult($method); + } + + $votes = $election->getVotesListAsString(); + } +} diff --git a/include/Condorcet/Benchmarks/KemenyYoungBench.php b/include/Condorcet/Benchmarks/KemenyYoungBench.php new file mode 100644 index 0000000..2b734f7 --- /dev/null +++ b/include/Condorcet/Benchmarks/KemenyYoungBench.php @@ -0,0 +1,47 @@ + ['candidatesCount' => $i]; + } + } + + + #[Bench\ParamProviders(['provideCandidatesCount'])] + #[Bench\OutputTimeUnit('milliseconds')] + #[Bench\Warmup(1)] + #[Bench\Iterations(3)] + #[Bench\Revs(4)] + public function benchKemenyYoung(array $params): void + { + $election = new Election; + + for ($i = 0; $i < $params['candidatesCount']; $i++) { + $candidates[] = $election->addCandidate(); + } + + $election->addVote($candidates); + + $result = $election->getResult('Kemeny–Young'); + } +} diff --git a/include/Condorcet/Benchmarks/MethodsNonProportionalBench.php b/include/Condorcet/Benchmarks/MethodsNonProportionalBench.php new file mode 100644 index 0000000..4dfaf74 --- /dev/null +++ b/include/Condorcet/Benchmarks/MethodsNonProportionalBench.php @@ -0,0 +1,89 @@ +election = $election = new Election; + $this->election->setNumberOfSeats(max(1, (int) ($numberOfCandidates / 3))); + $this->election->setStatsVerbosity(StatsVerbosity::STD); + + $candidates = []; + + for ($i=0; $i < $numberOfCandidates; $i++) { + $candidates[] = $election->addCandidate(); + } + + for ($i = 0; $i < $numberOfVotes; $i++) { + $oneVote = $randomizer->shuffleArray($candidates); + $election->addVote($oneVote); + } + } + + public function provideMethods(): \Generator + { + foreach (Condorcet::getAuthMethods() as $method) { + $class = Condorcet::getMethodClass($method); + + if ($class::IS_PROPORTIONAL === $this->IS_A_PROPORTIONAL_BENCH) { + yield $method => ['method' => $method]; + } + } + } + + public function provideNumberOfCandidates(): \Generator + { + foreach ($this->numberOfCandidates as $n) { + yield $n => ['numberOfCandidates' => $n]; + } + } + + public function setUp(array $params): void + { + $this->buildElection($params['numberOfCandidates'], 1_000); + } + + #[Bench\OutputTimeUnit('seconds')] + #[Bench\ParamProviders(['provideMethods', 'provideNumberOfCandidates'])] + #[Bench\BeforeMethods('setUp')] + #[Bench\Warmup(1)] + #[Bench\Iterations(3)] + #[Bench\Revs(1)] + public function benchByCandidates(array $params): void + { + try { + $result = $this->election->getResult($params['method']); + } catch (MethodLimitReachedException $e) { + } + + $this->election->cleanupCalculator(); + } +} diff --git a/include/Condorcet/Benchmarks/MethodsProportionalBench.php b/include/Condorcet/Benchmarks/MethodsProportionalBench.php new file mode 100644 index 0000000..fc620d1 --- /dev/null +++ b/include/Condorcet/Benchmarks/MethodsProportionalBench.php @@ -0,0 +1,21 @@ +election = $election = new Election; + $this->election->setNumberOfSeats((int) ($numberOfCandidates / 3)); + + $candidates = []; + + for ($i=0; $i < $numberOfCandidates; $i++) { + $candidates[] = $election->addCandidate(); + } + + for ($i = 0; $i < $numberOfVotes; $i++) { + $oneVote = $randomizer->shuffleArray($candidates); + $election->addVote($oneVote); + } + } + + public function provideNumberOfCandidates(): \Generator + { + foreach ($this->numberOfCandidates as $n) { + yield $n => ['numberOfCandidates' => $n]; + } + } + + public function provideNumberOfVotes(): \Generator + { + foreach ($this->numberOfVotes as $n) { + yield $n => ['numberOfVotes' => $n]; + } + } + + #[Bench\OutputTimeUnit('seconds')] + #[Bench\ParamProviders(['provideNumberOfCandidates', 'provideNumberOfVotes'])] + #[Bench\Warmup(0)] + #[Bench\Iterations(1)] + #[Bench\Revs(1)] + public function benchByCandidates(array $params): void + { + $this->buildElection($params['numberOfCandidates'], $params['numberOfVotes']); + } +} diff --git a/include/Condorcet/Benchmarks/PairwiseUpdateOptimizationBench.php b/include/Condorcet/Benchmarks/PairwiseUpdateOptimizationBench.php new file mode 100644 index 0000000..c62cf74 --- /dev/null +++ b/include/Condorcet/Benchmarks/PairwiseUpdateOptimizationBench.php @@ -0,0 +1,40 @@ +parseCandidates('A;B;C;D;E;F;G'); + + $election->parseVotes(' + E > B > C > A > G * 2500 + F > B > G > H > A * 2500 + H > B > G > E > A * 2500 + A = B = C > D > E = F > G * 2500 + G = E = C > F > A * 2500 + C > D = G > A > B * 2500 + '); + + $election->getWinner(); + + $vote = $election->addVote('A>B>C'); + + $election->removeVote($vote); + + $vote->setRanking('C>B>A'); + + $election->getWinner(); + } +} diff --git a/include/Condorcet/Benchmarks/SimpleUsageBench.php b/include/Condorcet/Benchmarks/SimpleUsageBench.php new file mode 100644 index 0000000..0f13542 --- /dev/null +++ b/include/Condorcet/Benchmarks/SimpleUsageBench.php @@ -0,0 +1,42 @@ +allowsVoteWeight(true); + + $election->parseCandidates('A;B;C;D;E;F'); + + $election->parseVotes(' + Ultimate Question of Life || A>B>C ^42 * 42 + C=A>B ^2 * 250 + B>C + E > B > C > A ^80 *257 + F > B > G > H > A* 250 + D = B = E > F ^6 * 100 + B>F=A>C * 100 + '); + + $winner = $election->getCondorcetWinner(); + $loser = $election->getCondorcetLoser(); + + $result = []; + + foreach (Condorcet::getAuthMethods() as $method) { + $result[] = $election->getResult($method); + } + } +} diff --git a/include/Condorcet/CHANGELOG.md b/include/Condorcet/CHANGELOG.md new file mode 100644 index 0000000..d08464a --- /dev/null +++ b/include/Condorcet/CHANGELOG.md @@ -0,0 +1,768 @@ +CHANGELOG +========= +All notable changes to this project will be documented in this file. + +## [v4.2.0] - 2022-08-23 +### Added +- Support for PHP 8.2 +- [Console] Redesign console, add many styles, colors, and logo. +- [Console] Improve interactive mode. + +### Changed +- [Methods] Fix a rare crash on HighestAverage methods. +- New namespace `CondorcetPHP\Condorcet\Utils`and move `CondorcetUtil` and `VoteUtil` classes to it. + +### Internal +- Refactor Vote entry parser to new class `CondorcetPHP\Condorcet\Utils\VoteEntryParser`: Better architecture and reduce code duplication. + +### Dev +- [Benchmarks] Use a random deterministic seed for generating votes. (Benchmarks now require PHP 8.2 at least) +- [Tests] Use a random deterministic if available (PHP >= 8.2) for generating votes. +- [Coding Style] Improve rules. + +## [v4.1.1] - 2022-07-07 +### Changed +- Composer: Fix ext-mbstring dependency. +- HighestAverage & LargestRemainder methods: Fix a bug with empty ranking in context (or non valid rank in a vote) + +## [v4.1.0] - 2022-06-26 + +### Description +Implements new proportional methods + +### Added +#### Voting Methods +- Sainte-Laguë / Webster method from HighestAverage method class. +- Thomas Jefferson / D'Hondt method from HighestAverage method class. +- Largest Remainder with all STV Quotas (Hare/Droop/Hagenbach-Bischof/Imperiali) as method options (like STV or CPO-STV). + +### Changed +- Default max length of a candidate name is now 100 UTF-8 characters instead of 30 + +### Internal changes +- Migrates the entire code base to a strict application of PSR-12 standards (and many other rules). It's debatable because our alternative syntax was lovely, but it's now standard and no longer iconoclastic, it's the age of reason. The respect for coding standards is now controlled by Laravel Pint. + +## [v4.0.0] - 2022-06-17 +### Description +Implement the CPO-STV method, the second official module for a proportional method. Comes with many performance improvements for some methods and elections with a lot of candidates. + +### Added +#### Core +- Adds `Election->setStatsVerbosity()` and `Election->getStatsVerbosity()` methods. This allows controlling the level of stats returned in the Result object by some methods. And to save memory and processing time if needed. +#### Voting Methods +- New proportional method: **CPO-STV** Look at the [VOTING_METHODS.md](VOTING_METHODS.md) for more details +#### TieBreaker +- New Tie Breaker method `TieBreaker::TieBreakerWithAnotherMethods`, a chaining method to break a tie +- Condorcet Election Format: Ability to parse candidates directly from votes, if not specified with the parameters first. According to the V1 specification of the format. + +### Changed +#### General +- Main source code moving to `src` folder instead of `lib` folder. Must change nothing if autoloaders are used. (but forks, if any, can need vigilance) +- A candidate's name can be equal to the string "0" +- Methods options can technically be string or array, first use for the CPO-STV tie breaker +- Use the native `JsonException` instead of custom `JsonFormatException`. + +#### Voting Methods +- Ranked Pairs default limit of candidates up from 40 to 60, thanks to performance optimizations +- Kemeny-Young now accepts 10 candidates by default thanks to performance optimizations. Up to 12 candidates without spending the night. And up to infinity without burning too much memory. +- `Throwable\CandidatesMaxNumberReachedException` used in Kemeny-Young and Ranked Pairs now extends `Throwable\MethodLimitReachedException`, and you should prefer to catch the second one. +- Some methods like Kemeny-Young or CPO-STV methods, send fewer stats than before to the `Result->getStats()` returning an array. To get back full details, you need to specify `Election->setStatsVerbosity(StatsVerbosity::FULL)` or `Election->setStatsVerbosity(StatsVerbosity::HIGH)`. This change speedup some methods and can use less memory. + +### Internal changes +#### Engine +- `Permutation` class was renamed to `Permutations` +- Use of `\SplFixedArray` in some methods, improving memory and performances in some cases and for some methods. +- Extensive rewriting of the Kemeny-Young voting method engine, twice as fast as before, consuming 42X less memory and allowing easy use 10-candidate with good performances. Do not use any disk cache file on disk anymore. However, since it uses real-time generators, reading its complete statistics (excluding rankings) may require more memory (but less than before). +- Many performance improvements, especially for some methods and elections with a lot of candidates. +- Methods can use a `Vote->getContextualRankingWithoutSort()` cache at the vote level, with a new internal API based on a WeakMap. +- New `Combinations` class. +- `Permutations` asn the new `Combinations` class can work internally with integer bigger than `PHP_MAX_INT` if installed from composer. But will always return an interger <= `PHP_MAX_INT` or throw a new exception `CondorcetPHP\Condorcet\Throwable\Internal\IntegerOverflowException` + +#### Dev +- Add Configuration for PHPStan +- Add and improve benchmarks +- Fix some env bugs for console tests +- New `InternalModulesAPI` attribute (yet unused in doc) + +## [v3.3.3] - 2022-05-02 + +### Changed +- ```Election::getVotesListAsString()``` will be ordered in a strict and predictable manner. +- Some methods (based on Borda, Majority, STV) they now work with a lower level of decimal precision. This avoids the accumulation of rounding differences, leading in some cases to tiny differences in statistics and extremely rarely, in differences in results (in case of extremely close elections with ties on ballot allowed) depending on the order in which the votes were entered in the election. This can result in unpredictable behavior that is uncomfortable for testing or accurate reproducibility. However, the behavior in these extreme cases can not be guaranteed 100%, even if it becomes even more rare and impractical. Pairwise methods (typically Condorcet methods) are not affected by this change. + +## [v3.3.2] - 2022-05-01 +### Added +- Condorcet Election Format: Compatibility with specification of ```/EMPTY_RANKING/``` keyword. (import and export) +### Changed +- Vote ranking from string input, cannot have empty rank (will be skipped silently instead of "" as candidate name) + +## [v3.3.1] - 2022-04-30 +### Changed +- Fix a regression on Kemmeny-Young stats since v3.3.0 (should not affect the final ranking, but still dangerous and incorrect). + +## [v3.3.0] - 2022-04-29 +### Description +This release adds Converters classes able to parse and convert to a Condorcet Election object, the textual votes file (synthetic, with all votes) from David Hill format, Debian format, and a new (and better) Condorcet format. An Election can also be exported to this new Condorcet format. + +Internally, circular references have been eliminated in favor of PHP8 weak references. It improve memory consumption before garbage collector call, prevents memory leaks from the PHP engine or Condorcet itself and removes some ugly code that was previously necessary to prevent them. + +### Added +#### Converters classes +- New Condorcet Format (specifications, converter to an Election, converter to a file) +- David Hill format (converter to an Election) +- Debian Format (converter to an Election) + +#### Console +- Options to import from Condorcet, David Hill, Debian formats. +- Bugfix: (display only) Wrong value for implicit voting + +#### Others +- ```Election::getVotesListAsString($withContext = true)``` get this new optional parameter, to ignore election context (restituate vote as they are submited, without any interpretation). + +### Internal changes +#### Structure +- Migration from circular references to PHP8 weak references. Improving memory consumption before garbage collector, and preventing memory leaks in the engine without any call (and sometimes successive call) to ```gc_collect_cycles()```. _Only complex users, creating simulations with many election objects and vote/candidates sharing between elections, in the same run, will experience factually the improvement. Or those who use some external data handler as Sqlite and needed to close connexion before the end of the script._ + +#### Internal API for methods modules or extended classes +- ```getLinks()``` internal API will now return a ```\Weakmap``` +- About methods module extending the abstract class ```Algo\Method```, the ```$_selfElection``` property is now a ```\WeakReference```. And must to exclude it from serialization. +- ```Algo\MethodInterface``` now required the ```setElection(Election $election)``` method because she is a part of unserialize from Condorcet unserialize process. + +======= +## [v3.2.3] - 2022-04-25 +### Changed +- Update the dev. dependencies and in particular phploc (broken) +- Use Symfony Console attribute 'AsCommand' (before deprecation) +- Bugfix: Infinite loop in Instant-Runoff method (very rare) +## [v3.2.2] - 2021-12-14 +### Changed +- Fix crash on ```Vote::addTags([])``` and add tests. +## [v3.2.1] - 2021-12-07 +### Changed +- Console: Improve column width, prevent most of table cuts depending on the width of the terminal. +## [v3.2.0] - 2021-12-01 +### Description +An important technical upgrade without many notable users changes. + +### Added +- Use PHP 8.1 new features (and minimal requirements). Resulting in more modern code & performances improvements. +- Result class now offers some publics (PHP 8.1) readonly properties in addition to getter methods. + +### Changed +- New Exceptions class and Exceptions hierarchies. Users must review all usage with exceptions captures. All new Exceptions extend the old CondorcetException class. But exceptions codes are no more used. (thanks @toddy15 for help and initiative) +- Improved documentation. +- Method::setOption() now only accept BackEnum or int parameter. +- StvQuotas parameters is now an Enum instead of a string. + +### Internal changes +- Improved and refactored documentation generator tool. +- Add some dev & quality tools like Infection resulting in code quality improvements and news tests (thanks @toddy15 for help and initiative). +- Reduce Docker image size + +## [v3.1.2] - 2021-11-23 +### Changed +Bug fix: Command-line fatal error on NULL natural Condorcet winner / loser #72 + +## [v3.1.1] - 2021-08-03 +### Added +- __CondorcetAutoloader check for the minimal PHP version #56 +### Changed +- Bugfix: KemenyYoung wrong results when using Election::removeCandidates() +- Various documentation fixes and improvements + +### Internal changes +- Docker: Upgrade Debian base image to Bullseye + +## [v3.1.0] - 2021-08-03 +### Description +Introduces support for proportional methods. And adds some. + +### Added +- Minimal support for proportional methods +- New Single Transferable Vote (STV) method, a proportional one. +- New Options system for methods with parameters. Easier to use that extend and register class. Include new API methods. +- New methods Multiple Round system replace Two-round system and takes advantage of the method options. This change is backward compatible. +- Add IRV as an alias for Instant Runoff #48 + +### Changed +- getResult() Fix arbitrary candidate ordering on a rank. Now have consistency with getRanking and others. +- Some console improvements in style. +- Console: Shorten show-method-stats to method-stats #49 +- Compatibility with PHP 8.1 + +### Internal changes +- Small optimizations for InstantRunoff +- Static class for tie-breaking. Used by InstantRunoff but can be useful for further uses. + +### Dev changes +- CondorcetDocumentator now using PHP8 attribtues. + +## [v3.0.2] - 2021-04-28 + +### Changed +- Fix typos in command line options +- Add command line option: --show-method-stats + +## [v3.0.1] - 2020-12-08 + +### Changed +- Bugfix Issue #32 + +## [v3.0.0] - 2020-12-01 +### Description +Use latest PHP 8.0 functionality and improvements. + +### Added +- Documentation tools code is now included in this main repository instead of composer dependency. (and it's completely refactored) +- Most of the documentation use PHP8 Attributes instead of a big Yaml base file. + +### Changed +- Refactoring Kemeny–Young code and cache system. +- PHP8 syntax and functionality. + +## [v2.2.1] - 2020-10-17 +### Changed +- Timer functionality is now deactivated by default for performance reason. Can be reactivated manually: ```php Condorcet:$UseTimer = true;``` +- Code cleanup and update for PHP 8.0 + +## [v2.2.0] - 2020-05-18 +### Description +Include the new vote method "Two-round voting System", and some minors fix and optimizations. + +### Added +- Two-round voting system. Useful to compare Condorcet with traditional lections. +- New Majority_Core class is the parent class for First-Past-The-Post and two-round system. It can easily be extended with variant parameters or code for creating easily many new majority voting methods (number of rounds, number of candidates retained after each round...). + +### Changed +- Complete rewrite of First-Past-The-Post code. No user changes. +- Provides the right acronyms for First-Past-The-Post voting method. + +### Internal changes +- Developments dependency upgrades. + +## [v2.1.0] - 2019-12-29 +### Description +Move to PHP 7.4 version. And add a console application for command line usage! + +### Added +- New Condorcet Application from the command line. Easy to use for non-technical users. And faster to use for everyone for most use cases. +- Docker file for building Condorcet image. For very easy access to the command line application. +- Official compiled PHAR files are now available for each version on the release page. +- Json input can include vote weight. +- New method ```Election::parseVotesWithoutFail()``` allowing to ignore the bad vote, and useful for low and constant memory usage. + +### Changed +- PHP 7.4 is the new minimal PHP version required. +- MBSTRING PHP extension is not requiered anymore. +- ```Election::allowVoteWeight()``` renamed to Election::allowsVoteWeight() +- ```Vote::getSimpleRanking()``` allow a new optional parameter $displayWeight. +- ```Vote::getWeight()``` allow a new optional parameter $context and if providing returning ranking in context of an election instead theorical ranking. + +### Internal changes +- Use PHP 7.4 new types on property. For security and performance reasons. + +## [v2.0.1] - 2019-12-29 +### Description +The 2.0 branch is now legacy. + +### Changed +- Harmonize parseVotes & parseJson +- Use Github Actions instead of Travis CI. + +## [v2.0.0] - 2019-09-07 +### Description +Small but many API changes (renaming, rationalization), sometimes on main methods. Very important internal optimizations. Also, use a new namespace! + +### Added +- Very significant efforts about the documentation. Still in progress. +- Add two distincts methods Election::getCondorcetWinner() & Election::getCondorcetLoser(). the result are strictly equivalent to ```Election::getWinner(null)``` & ```Election::getLoser(null)``` but it's more explicit. And it is consistent with the homonymous methods of the result object. + +### Changed +- Use new vendor namespace ```\CondorcetPHP``` instead ```\Condorcet``` because we don't have the Github Condorcet ID for this last one. The new base namespace is ```\CondorcetPHP\Condorcet\``` +- Adding votes or candidates from Json or string parsing, will not check if all of them are valid. And only if all are valid, then they are registered. Previously, an exception was sent to the first error, but the status remained partially recorded. +- Many methods have been renamed or divided for greater consistency and intelligibility. Some parameters may have changed or used a more strict type. +- CondorcertException class moves to a new namespace ```Throwable\CondorcetException```. Condorcet can now also throw Trowable\CondorcetInternalError error. +- Some change on "Schulze Ratio" method computation, can affect the result of some type of election. It's still imperfect due to a case that the theory doesn't take into account. However, very small or very typical elections will have more logical results. The more common Winning & Margin methods are not affected. +- Many other various fixes. +- New Condorcet logo. + +### Removed +- Removes the slightly twisted method Election::ignoreMaxVote + +### Performance +- Adding, changing or removing vote after requesting the firsts Result, will prevent recomputing all the pairwise and prefer to update it. Can be a small optimization for most use case, or a very huge performances improvements if you manage a very large number of votes or use an external data handler for storing them. +- Parse Vote (text or Json) is faster, save huge memory consumption on big input if you use the if use the multiplication symbol for equal votes. And memory is now predictable, linear, and as smart as other input vote methods. +- Overall, significant savings in memory usage. +- Many other performance optimizations. + +### Internal changes +- Voting Method extending PairwiseStatsBased_Core must use static constant COUNT_TYPE instead of $\_countType property. +- Add many tests. +- Add benchmarks with phpbench. + +## [v1.8.2] - 2019-02-16 +### Description +Allow development environment for PHP 7.4. + +### Changed +- Use PHP UNIT 8.x (compatible up to PHP 7.4) for PHP version >= 7.2. +- Compatibility with PHP Unit 8.x serie + +## [v1.8.1] - 2019-01-05 +### Description +Tests for PHP 7.3 and prevent PHP >=7.4 deprecations. + +### Added +- Tests for PHP 7.3 +- Prevent PHP 7.4 deprecations. + +## [v1.8.0] - 2018-07-22 +### Description +You can add a custom constraint to force votes to meet certain criteria. +Constraints are designed as free modules (external class to load into each election). A first module is officially proposed (disabled by default): Preventing votes from including ties. + +### Added +- Vote constraints functionality +- First official vote constraint module: Disallow vote tie on any rank in the election context. +- Tested with PHP 7.3. +- Always better code! + +## [v1.7.0] - 2018-05-26 +### Description +Adds for the first time methods unrelated to Condorcet's criteria (Borda, Alternative Voting, FTPT). + +### Added +- New voting method: Borda Count +- New voting method: Dowdall System (Nauru), a Borda Count variant. +- New voting method: Instant-runoff (also know as Alternative Voting or Preferential Voting) +- New voting method: First-past-the-post + +### Changed +- Result object (iterator and his other methods) is now ordered by key from the first rank to the last rank. However, no method officially implemented before version 1.7 was affected by this theoretical problem. This never affected the correct rank allocation but could cause misinterpretation if carelessly repeated in a loop. + +### Internal changes +- Code cleanup & test improvements + +## [v1.6.0] - 2018-01-28 +### Description +Publication centred on two axes: +Firstly, the improvement and finishing of the internal structure, often for the purpose of readability of the code and good practices. And an extension of the tests. +The second part concerns the continued improvement of the management of the very large elections. + +### Added +- Add ```\CondorcetPHP\Election::getVotesListGenerator``` and ```\CondorcetPHP\DataManager\VotesManager::getVotesListGenerator``` methods. Same as getVotesList method, but output a PHP generator instead of full array. Useful only for working on vera large election with an external DataHandler. + +### Changed +- ```\CondorcetPHP\Condorcet::format()``` method move to \CondorcetPHP\CondorcetUtil::format() +- ```\CondorcetPHP\CondorcetUtil::format()``` can no longer optionally produce a var_dump(). You must do it yourself. +- Remove ```\CondorcetPHP\Election::getVoteByKey(```) method +- Fix ```\CondorcetPHP\Election``` cloning issues +- Simply DataHandlerDriverInterface +- Optimize ```\CondorcetPHP\Election::countVotes``` and ```\CondorcetPHP\DataManager\VotesManager::countVotes``` methods performance and memory usage in case of using an external DataHandler. + +### Internal changes +- Cut out some classes and functions into smaller sub-groups. +- New dev tools in a new dedicated folder (update documentation, generate Kemeny–Young cache) +- Documentation generator code move to a new repository +- Git force Unix style line-ending (This could cause unexpected behavior in some tests related to the export of votes in string output.) +- Various optimizations & bugfix +- Test coverage up to 95% +- Wonderful new files headers + +## [v1.5.0] - 2018-01-08 +### Description +This release focuses on the management of very large elections. +It more rigorously reviews the functioning of the DataHandler, which is an advanced way to manage a very large number of votes, which is more stable, mature and tested. +It adds, as an alternative and as a complement (both can be used in consort) the notion of the weight of a vote. This may be useful for elections in which voters are not equal. Or to emulate a big election (without too many possible combinations!) if you don't need to store the details of each vote at Condorcet level. + +### Added +- It is now possible to add weight (integer >= 1) to each vote. If you enable this mode at the election level (deactivated by default) then the votes will be proportional to their weight when calculating all the algorithms. for example, if the weight is 2, then the vote will count double. +This is an alternative and complementary to adding multiple votes. Using this mode of operation can save you (for large elections) a high cost in RAM or the configuration/development of a DataHandler, which can be complex. However, if you need to keep the information of each elector at Condorcet level, this functionality will not satisfy you, it is useful if at this level the voting information is useless or if it makes no sense. +- Using a DataHandler to externalize vote data is now compatible with vote tags. + +### Internal changes +- More mature and tested management of external DataHandler. Your custom drivers need to be updated. +- News tests +- Minors clean-up, changes & optimizations + +## [v1.4.1] - 2017-12-21 +### Changed +- Add some aliases for Ranked Pairs + +## [v1.4.0] - 2017-12-11 +### Description +Rewrite and extend old and experimental implementation with a new one. After a lot of reading and some exchanges with Nicolaus Tideman. + +### Added +- Ranked Pairs method is no more experimental thanks to a completely new implementation. It has also been divided into two methods: "Ranked Pairs Margin" which is favored by Nicolaus Tideman and Ranked Pairs Winning which is the variant most often mentioned in other documentation, including the English Wikipedia. +Regarding compatibility with the old version, "Ranked Pairs" or "RankedPairs" calls now use Ranked Pairs Margin. Note that the old implementation, although failing in other respects, used a Winning version. which in some very specific cases can change your results. +*[More information on Condorcet Wiki](https://github.com/julien-boudry/Condorcet/wiki/I-%23-Installation---Basic-Configuration-%23-2.-Condorcet-Methods)* + +- New method: [Election::getVotesListAsString](https://github.com/julien-boudry/Condorcet/blob/master/Documentation/Election%20Class/public%20Election--getVotesListAsString.md) +- New method: [Result::Election::getResultAsString](https://github.com/julien-boudry/Condorcet/blob/master/Documentation/Result%20Class/public%20Result--getResultAsString.md) + +### Internal changes +- News tests +- Minors clean-up, changes & optimisations + +## [v1.3.4] - 2017-11-29 +### Fixed +- Potentially backward-incompatible change if you use the rare and useless Dodgson method** + Fix #19 (thanks to @janmotl) : Our implementation of Dodgson was wrong. Or rather: used Tideman's approximation without knowing it. This could potentially lead to false results in some cases on this method. + Dodgson's original will not be implemented because of the work it would represent and strong performance limitations (number of candidates, number of votes). + + Instead, we kept "Tideman approximation" but renamed it. And we added another approximation: "Dodgson Quick". We recommend this second solution, being the best of both, even if the two may be right in different cases or wrong together. + More explanations here: https://www.maa.org/sites/default/files/pdf/cmj_ftp/CMJ/September%202010/3%20Articles/6%2009-229%20Ratliff/Dodgson_CMJ_Final.pdf + + No longer any method use the call "Dodgson", please look at the [documentation](https://github.com/julien-boudry/Condorcet/wiki/I-%23-Installation---Basic-Configuration-%23-2.-Condorcet-Methods) to know which calls to use for either one. + +## [v1.3.3] - 2017-10-04 +### Fixed +- Critical bugfix: Result cache was frozen after serializing/unserializing an Election object. You can register or remove votes, but each result eventually computed before serializing stays frozen and potentially false. +If you do not store the Election object by serializing it, you are not affected. + +## [v1.3.2] - 2017-09-24 +### Fixed +- Minor bugfix release. Fix PHP errors on very small elections (Ranked Pairs / Minimax). + +## [v1.3.1] - 2017-09-18 +### Fixed +- Bugfix : ```Vote::getSimpleVote()``` +- ```Vote::getContextualVote()``` is renamed ```Vote::getContextualRanking()``` + +## [v1.3.0] - 2017-09-17 +### Description +Optional management of a new mode: if a vote does not specify the last rank. Then there is no last rank. This can significantly change the outcome of an election. +Previously (and still by default), if the last rank was not specified, it was automatically deducted. +This functionality is managed at the level of an election, it is possible to switch from one to the other without affecting the votes. The latter may even participate simultaneously in several elections using different modes. + +Also add Dodgson method. + +### Added +- Adds [Dodgson method](https://en.wikipedia.org/wiki/Dodgson%27s_method) +- Previously, if within a ranking, you do not specify all candidates participating in an election. It was considered that you placed the missing ones implicitly on the last rank. +This is always the default behavior. Alternative behavior is now added so that the missing candidates are ignored when calculating the results, which can significantly change the results. Please refer to the documentation. +- Add Kemeny-Young 'bestScore' information on Result::getStats() + +### Changed +- If you vote using Candidate object. They will no longer be converted into another Candidate object with the same name if this one exists into the target election. They will be two different candidates... with the same name. It's more strict and prevents error in this strange case. +However, it can strongly be advised to not to mix different candidates with the same name, so that you do not mislead yourself! However, Condorcet now manages this case correctly. +If you vote by string or Json input : Nothing changes. Either a new candidate will be created or they will be converted with the candidate object with the same name in the first election of the voting object in which they will participate. +- Improved Condorcet::format() + +### Fixed +- Fix Result::getWinner && Result::getLoser() +* Vote:getContextualVote() return stricter rank array key, from 1 to n. + +### Internals changes +- Adds [PHPUnit](https://phpunit.de/) with many tests in particular to monitor the results. +- Refactoring Copeland and Minimax code. With the new Dodgson method, they now all using the same lightweight code based on the improved PairwiseStats class. +- Many code refactoring. + +## [v1.2.3] - 2017-09-03 +### Changed +- The method ```Candidate::getName``` no longer takes an argument. + +### Fixed +- More strict array key on ```Vote::getContextualVote``` method +- Various bugfix +- First PHP Unit tests + +## [v1.2.2] - 2017-08-31 +### Fixed +- Bugfix & Support for PHP 7.2 +- Bugfix for KemenyYoung conflict detection API + +## [v1.2.1] - 2017-07-28 +### Fixed +- Bugfix Version ( thanks @hboomsma : https://github.com/julien-boudry/Condorcet/issues/15 ) + +## [v1.2.0] - 2016-12-11 +### Changed +- Use the news PHP 7.1 syntax. PHP 7.1 is now require for 1.2.x branch and above. + +## [v1.1.0] - 2016-09-11 +### Added +- New Result class. The results are no longer provided as array but as a result object. Coming with many methods (get it as an array, check metadata, get others results from infos at the generation time...). It implements Iterator / Countable / ArrayAccess for excellent backward compatibility. +- Kemeny-Young paradox is now run more cleanly thanks to the new result object. + +## Changed +- Using PHP new syntax and optimization. PHP 7.0 >= is now required for 1.1 branch. +- Improve multi-elections contexts. Now works in a manner consistent with what you want to do (and what yet allowed to imply the documentation). +- Efforts on documentation (wiki), and wide rewriting examples. +- Many internals refactoring and bugfix. + + +## [v1.0.0] - 2016-06-05 +### Added +- **Experimental support for very large election**, it comes with critical internal changes. A functional driver for PDO is provided to be used with relational SQL databases, you will probably need to extend it or configure it (database structure...).
+ The modular structure allows you to develop your own implementation to get outsourced datastore, for example, we can imagine a NoSQL database driver.
+ Benchmark shows that on PHP 7 + SQLite, 200 000 votes can be registered and computed in less than 60 seconds on a little server, with ~60mb RAM use. However, the speed of the driver does not change much the performance. From a certain point: slowdowns are intrinsically linked to the internal processing side Condorcet engine. Major optimizations for speed can easily be done for further releases, but this would require a trade-off between speed and code complexity.
+ _If you are interested by this feature, please have a look to the documentation. Consider that the functionality come in BETA stage._ +- New method Vote::getSimpleRanking -> Provide vote ranking as a string in one line. (Ex: 'A>B=D>C') +- New CondorcetPHP\Algo\Tools\VirtualVote:: removeCandidates(Condorcet/Vote $vote, array $candidateToRemove) static method clone your vote and return this clone without specified candidates. + +### Changed +- Requirement: PHP 5.6 is the new minimal PHP version. Full official support for PHP7 is now provided (and include some bug fixes). +- Internal Change: Many Cleans & Improvements + +## [v0.97.0] - 2015-09-05 +### Changed +- Internal change, for future management ability of billions of votes. +- Timer manager knows give an historic with CondorcetPHP\Timer\Manager->getHistory(); + +## [v0.96.0] - 2015-08-22 +### Changed +- Condorcet autoloader move on Condorcet/__CondorcetAutoload.php folder, outside lib folder. If you don't have your owns PSR-4 autoloader, you must now explicitly include this file, +- Because of dramatically random performance beyond 7 Candidates, our flawed implementation of RankedPair method is now by default limited to 7 Candiates. + More than ever, the implementation of RankedPair should be considered experimental; it's really the only method giving me serious problems. +- Some minor internal changes. + +## [v0.95.1] - 2015-08-15 +### Fixed +- Bugfix version + +## [v0.95.0] - 2015-08-15 +### Changed +The new class Election replaces Condorcet class and retains most of its methods, static or not. +The class Condorcet survives. And now takes care of core configuration, such as recording modules. + +So you must now create an object Election instead of Condorcet. What is more explicit. The remaining methods related to class Condorcet are in the documentation and examples. +As the class keeps the Condorcet high static method (although specialized), code modification on your part will be very minor and generally involve changing the "new Condorcet" instruction to "new Election". + +- Condorcet::getClassVersion is renamed Condorcet::getVersion + +## [v0.94.0] - 2015-08-14 +### Added +- Method name now have alias. + So _Condorcet::getResult('Schulze')_ is now strictly equivalent to _Condorcet::getResult('Schulze Winning')_ or _Condorcet::getResult('Schulze_Winning')_ or the class namespace CondorcetPHP::getResult('CondorcetPHP\Algo\Methods\SchulzeWinning'). + +### Changed +- Condorcet:addAlgos is renamed to Condorcet::addMethod, it's more logic. Argument is now a fully-qualified class name. This class can now be outside of \Condocet namespace. Adding your own algorithm is now much cleaner. +- Condorcet::getPairwise(false) now return the new pairwise object instead of an abstract array. Condorcet::getPairwise(true) is unchanged and equivalent to Pairwise::getExplicitPairwise. + The new pairwise object implement \Iterastor and \ArrayAccess interfaces, so change may be transparent for you. +- PSR-0 autoloader support is removed. Instead, Condorcet is compliant with any PSR-4 autoloader, and it is now the only way to use it with Composer. + If you don't have PSR-4 autoloader or you don't want to use Composer, you can continue to just include lib\Condorcet.php, its use now his own fallback PSR-4 implementation instead of his old PSR-0 implementation. + Be careful, path to Condorcet.php change cause of PSR-4. It's now lib/Condorcet.php and not the old lib/Condorcet/Condorcet.php old path. + +# Internal changes +- Reorganization about namespacing. +- Methods are now loaded by the normal and common autoloader. +- Pairwise is now an independent object, distinct from Condorcet object. +- Timer functionality now works outside of Condorcet class. With Timer\Manager and Timer\Chrono class. More improvement coming later (full log for benchmarking). You can get the timer manager by Condorcet::getTimerManager, but then know what you do! +- And other little things and optimization (deleting code deduplication, new constants instead of hardcoding...). + +## [v0.93.0] - 2015-08-02 +### Changed +- Minor internals optimizations. +- Some coding conventions change (can affect many lines). + +### Fixed +- If you try to get Kemeny-Young 9 candidates on a Stable branch, Kemeny-Young will not try to store generic cache data on disk. This usage (although not recommended in all cases) is slower than before. but does not attempt to do something that should not be possible. + +## [v0.92.0] - 2015-07-26 +### Changed +- Deleting concept of Condorcet object default algorithm, and Class force algorithm. Now, there is only a Class Default method. + _Now the default method is managed as static. It is no longer possible to force a method. Any management of the default object-level method is deleted + This results in a simple boost, and therefore easier to understand documentation without gadgets methods. + A slight speed increase can also be observed, the code being rid of a complex management resulting in many internal calls._ +- Support PSR-4 autoloading with composer. +- New implementation of PSR-0 autloader. _(Automatically used if you do not go through one provided by Composer or framework or other valid autoloaders. It acts only fallback of last resort)_. +- Some internal code cleanup (organizational change with sub-namespace, moving or rename some methods or class, better Schulze Family strategy). + +## [v0.91.0] - 2015-03-28 +### Changed +- Condorcet now use more usual PSR-0 class loading philosophy. And each Class and Interface has now her owns PHP file according to PSR-0 specifications. + - As result, the new architecture from Condorcet 0.90 is now fully compatible with framework (composer autoloader), is case of you would to play with Candidate Class (for example) before creating first an election by Condorcet Class. It's was a serious issue from Condorcet 0.90. + - You can too continue to include /lib/Condorcet/Condorcet.php file as loader, if there is no others compatible autoloader, Condorcet will now use a new and special PSR-0 like autoloader for himself. +- Documentation files now use filename compatible with Windows filesystem (thanks Bill ^^) +- Minor and unnecessary coding style change. + +## [v0.90.0] - 2015-02-14 +### Description +New internal architecture. very important code refactoring, often completely rewritten. Relatively new API. + +### Added +- Algorithms are now more isolated than Condorcet activities, they use them by their own API. + +#### Vote & Candidate are now objects! +- Candidate and Vote are now objects. You can continue to provide string, but all will be converted into object and the return values of most methods will favor this new philosophy. +- Vote and candidate are independent objects. They can participate in various elections simultaneously. They have their own lives and historical (name change, change of vote, elections to which they are taking or no longer taking part ...). + They can be cloned, serialized, analyzed... +- Like Candidate object, a Vote object can take part into multiple elections. He can change its ranking and it will automatically affect all its elections. You can also provide top ranking, and its election can have other candidates. Condorcet will intelligently reconstruct a context for each election even if they do not have the same list of candidates! +- Off course, you can extend them ! + +#### Kemeny-Young improvements +- Code review. +- New Permutation class. 9 maximum candidates instead of 6! So, for performance reasons, I suggest to stay at 8. + Thanks to Jorge Gomes (@cyberkurumin) for his helpful commit! + +#### Documentation + +Documentation more consistent with the new size of the library has been established. It is not perfect yet, but the documentation work continues day after day to make up for the delay. + +However, foundations and most contents are available now. +- New examples of codes meeting the latest revolutions. +- A new manual, in the form of a Wiki Github. +- Complete specifications for each of the public methods. Into the _doc_ directory. + +### Changed +- **Condorcet::format** static method is a substitute to var_dump to print easily better human readable Condorcet data (Vote, Résult). It can also be used to return (and not print) more simple dataset. +- Many new methods or API change. But structure stay similar, and old simple scripts can continue to work without modifications or really minor changes. + +## [v0.14.0] - 2014-08-10 +### Description +The code will be very severely rewritten and restructured for the next major release. + +### Added +- Added UNIX timestamp of the record of each vote time as a special tag. +- Ability to perform cryptographic checksum (SHA-2 256) the status of an election (candidates, votes, cache, library version). +- New static method setMaxVoteNumber, allow you to limit the number of votes in a election. And public method to ignore it (or not) for each object. +- Improvements and bugfixes around object serialization. +- New Options for getClassVersion method + +### Changed +- Compatibility is now guaranteed from PHP 5.5.12 to PHP 5.6.x. But the vital methods seem functional with PHP (>=) 5.4.3 +- Algorithm Kemeny-Young V2: more than 1000 times faster with a cache of pre-computed data. 6 candidates on an election is now very fast, and it is the new provisional limit. + Next Condorcet version will allow more candidates for Kemeny-Young (7 or 8), with more pre-computed sets. +- Customized limitation of maximum candidate for Kemeny-Young is removed. +- Works around the presentation of the single primary tag of each vote + +### Fixed +- Improvements and bugfixes around object serialization. +- Many bugfixes and minor internal adjustments. Mostly to satisfy the development of condorcet-vote.org, which uses the library as a real framework of election management. + +## [v0.13.2] - 2014-07-29 +### Fixed +- Bugfix on getVotesList() and all tag filter methods + +## [v0.13.0] - 2014-07-06 +### Added +- New logo by @Christelle-Radena +- Add getLastTimer() and getGlobalTimer() methods + +### Changed +- isJson is now a static method, useful for Condorcet API project +- Add an exception handler into the examples + +### Fixed +- Some minors bugfix about CondorcetException class + +## [v0.12.0] - 2014-07-02 +### Added +- Votes and candidates can now be defined by a json input. _( jsonVotes(), jsonCandidates() )_ +- Candidates can now be defined by a text input _( string or file with parseCandidates() )_ +- The input text or json can now take a parameter of anti-flood safety generating an exception, providing you foresee yourself. + +### Changed +- (Git) The static method ::getClassVersion() returns 'DEV' entitle on developments branches. +- Candidate name are now trim() +- The old system of errors reporting is deleted. +- The class now throws exceptions of class 'CondorcetException' + +### Fixed +- Various bugfix + +## [v0.11.1] - 2014-07-01 +### Fixed +- Minor bugfix for getMethod() & setMethod() methods. + +## [v0.11.0] - 2014-06-14 +### Added +- Added the ability to include a multitude of votes votes simultaneously from a text or a single wide string file. +- Adding an adjustable anti-flood on the previous method. + +- More flexibility to register or claim tags. Use an array or a string separated by commas. +- The countVote() method can now act on specific tags. +- The getVoteList() method can now be used more accurate and extensive. +- The removeVotes() method can now be used more accurate and extensive. +- The getResult() method can now be used to gain a profit on a partial selection of the votes (using tags) without requiring the prior removal of votes. +- Added more specific error message on addVote() + +### Changed +- Improved documentation +- Various optimizations + +## [v0.10.1] - 2014-05-17 +### Fixed +- Bugfix for Schulze_Ratio (division by 0) + +## [v0.10.0] - 2014-05-13 +### Added +- New variants for the method of Schulze +- Experimental Implementation of the Ranked Pairs method + +### Changed +- Audited successfully Schulze with the example of Martin Schulze itself +- Various optimizations + +## [v0.9.0] - 2014-05-01 +### Added +- Ability to add options to the algorithm with getResult () +- KemenyYoung is now able to detect its conflicts and inform through the use of an option on getResult () (see the documentation) + +### Changed +- Multiple bug fixes, some important (but never affecting the results). +- Code harmonization +- On the benefit of public methods provide a return value with the meaning and utility. +- Isolation of static methods having intended to be used by algorithms to avoid duplication of one another. +- Very complete (but not 100% exhaustive) examples of uses are kindly provided. +- Global code review by the author. + +## [v0.8.0] - 2014-04-24 +### Added +- New algorithm: Kemeny-Young (http://en.wikipedia.org/wiki/Kemeny-Young_method) + +### Changed +- Many internal improvements, code cleanup and improve modularity rules +- Candidates can now have long names (30 characters) + +## [v0.7.0] - 2014-04-18 +### Added +- Support for serialize and unserialize object. + - Optimization of data to keep back. + - Checking the version of the data to import. + - WARNING : Resultat will be recalculated from voting data. + +### Changed +- Complete harmonization of attribute names and methods. Compatibility with old API is completely broken on the form, but the operation remains exactly the same. +- Many code cleanup and optimizations + +## [v0.6.0] - 2014-04-14 +### Added +- Add new Condorcet algorithm: Minimax in its three variants (Winning, Margin, Opposition | The last one is not Condorcet criterion compliant) + http://en.wikipedia.org/wiki/Minimax_Condorcet +- Add the ability to record a new vote by using a format string like "A>B=C>D" rather than the use of an array, read the doc! + +### Fixed +- Some bugfix & optimizations + +## [v0.5.1] - 2014-04-13 +### Fixed +- Bugfix about registering new algorihms by an array + +## [v0.5.0] - 2014-04-13 +### Added +- Support Composer + +### Changed +- Important structural changes to meet the standard PSR-0, PSR-1 and PSR-2 (partial). + +## [v0.4.0] - 2014-04-06 +### Description +First version ready for production, with many API improvements and news features. And the new Condorcet Copeland method. + +## [v0.3.0] - 2014-03-16 +### Description +Considerable structural changes and redesign many parts of API. +The class can now support the easy addition of new algorithms. + +The next release will be devoted to validate the apparent stability of this version 0.3 and enrich the API for more flexibility. And perhaps the arrival of a new algorithm. + +## [v0.2.0] - 2014-03-12 +### Description +Second release! Not really ready for production, please test it! + +## [v0.1.0] - 2014-03-09 +### Description +First release! Not really ready for production, please test it ! diff --git a/include/Condorcet/CONTRIBUTING.md b/include/Condorcet/CONTRIBUTING.md new file mode 100644 index 0000000..792834e --- /dev/null +++ b/include/Condorcet/CONTRIBUTING.md @@ -0,0 +1 @@ +Please help. \ No newline at end of file diff --git a/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Description.php b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Description.php new file mode 100644 index 0000000..2691c6a --- /dev/null +++ b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Description.php @@ -0,0 +1,24 @@ +text = $text; + } +} diff --git a/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Example.php b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Example.php new file mode 100644 index 0000000..1343721 --- /dev/null +++ b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Example.php @@ -0,0 +1,26 @@ +name = $name; + $this->link = $link; + } +} diff --git a/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/FunctionParameter.php b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/FunctionParameter.php new file mode 100644 index 0000000..68828d8 --- /dev/null +++ b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/FunctionParameter.php @@ -0,0 +1,24 @@ +text = (mb_substr($text, -1) === '.') ? $text : $text.'.'; + } +} diff --git a/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/FunctionReturn.php b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/FunctionReturn.php new file mode 100644 index 0000000..1115640 --- /dev/null +++ b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/FunctionReturn.php @@ -0,0 +1,24 @@ +text = $text; + } +} diff --git a/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/InternalModulesAPI.php b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/InternalModulesAPI.php new file mode 100644 index 0000000..f2737ac --- /dev/null +++ b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/InternalModulesAPI.php @@ -0,0 +1,18 @@ +relatedList = $relatedList; + } +} diff --git a/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Throws.php b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Throws.php new file mode 100644 index 0000000..2208b00 --- /dev/null +++ b/include/Condorcet/Dev/CondorcetDocumentationGenerator/CondorcetDocAttributes/Throws.php @@ -0,0 +1,24 @@ +exceptionList = $exceptionList; + } +} diff --git a/include/Condorcet/Dev/CondorcetDocumentationGenerator/Generate.php b/include/Condorcet/Dev/CondorcetDocumentationGenerator/Generate.php new file mode 100644 index 0000000..3634e8e --- /dev/null +++ b/include/Condorcet/Dev/CondorcetDocumentationGenerator/Generate.php @@ -0,0 +1,580 @@ +class)).'--'.$method->name. + '.md'; + } + + public static function simpleClass(string $fullClassName): string + { + return str_replace('CondorcetPHP\\Condorcet\\', '', $fullClassName); + } + + public static function speakBool($c): string + { + if ($c === true || $c === 'true') { + return 'true'; + } + if ($c === false || $c === 'false') { + return 'false'; + } + if ($c === null || $c === 'null') { + return 'null'; + } + if (\is_array($c)) { + return '['.implode(',', $c).']'; + } + if (\is_object($c)) { + return 'new '.$c::class; + } + + return (string) $c; + } + + public static function getTypeAsString(?\ReflectionType $rf_rt, bool $codeBlock = false): ?string + { + if ($rf_rt !== null) { + if ($codeBlock) { + return '```'.((string) $rf_rt).'```'; + } else { + return (string) $rf_rt; + } + } + + return $rf_rt; + } + + public static function getModifiersName(\ReflectionMethod $method): string + { + return implode(' ', \Reflection::getModifierNames($method->getModifiers())); + } + + // Static - Builder + + public static function cleverRelated(string $name): string + { + $infos = explode('::', $name); + $infos[0] = str_replace('static ', '', $infos[0]); + + $url = '../'.$infos[0].' Class/public '.str_replace('::', '--', $name) . '.md'; + $url = str_replace(' ', '%20', $url); + + return '['.$name.']('.$url.')'; + } + + public static function computeRepresentationAsForIndex(\ReflectionMethod $method): string + { + return self::getModifiersName($method). + ' '. + self::simpleClass($method->class). + (($method->isStatic()) ? '::' : '->'). + $method->name. + ' ('.(($method->getNumberOfParameters() > 0) ? '...' : '').')'; + } + + public static function computeRepresentationAsPHP(\ReflectionMethod $method): string + { + $option = false; + $str = '('; + $i = 0; + + + if ($method->getNumberOfParameters() > 0) { + foreach ($method->getParameters() as $value) { + $str .= ' '; + $str .= ($value->isOptional() && !$option) ? '[' : ''; + $str .= ($i > 0) ? ', ' : ''; + $str .= self::getTypeAsString($value->getType()); + $str .= ' '; + $str .= $value->isPassedByReference() ? '&' : ''; + $str .= '$'.$value->getName(); + $str .= $value->isDefaultValueAvailable() ? ' = '.self::speakBool($value->getDefaultValue()) : ''; + + ($value->isOptional() && !$option) ? $option = true : null; + $i++; + } + } + + if ($option) { + $str .= ']'; + } + + $str .= ' )'; + + return "```php\n". + self::getModifiersName($method).' '.self::simpleClass($method->class).(($method->isStatic()) ? '::' : '->').$method->name.' '.$str. ((self::getTypeAsString($method->getReturnType()) !== null) ? ': '.self::getTypeAsString($method->getReturnType()) : ''). + "\n```"; + } + + + // Script + public function __construct(string $path) + { + $start_time = microtime(true); + + $pathDirectory = $path.\DIRECTORY_SEPARATOR; + + // + $FullClassList = ClassFinder::getClassesInNamespace('CondorcetPHP\Condorcet\\', ClassFinder::RECURSIVE_MODE); + $FullClassList = array_filter($FullClassList, static function (string $value) { + return (mb_strpos($value, 'Condorcet\Test') === false) && (mb_strpos($value, 'Condorcet\Dev') === false); + }); + + $inDoc = 0; + $non_inDoc = 0; + $total_methods = 0; + $total_nonInternal_methods = 0; + + // Warnings + foreach ($FullClassList as $FullClass) { + $methods = (new \ReflectionClass($FullClass))->getMethods(\ReflectionMethod::IS_PUBLIC); + + foreach ($methods as $oneMethod) { + if ($oneMethod->isInternal()) { + } elseif (!empty($oneMethod->getAttributes(PublicAPI::class))) { + $inDoc++; + + if ($oneMethod->getNumberOfParameters() > 0) { + foreach ($oneMethod->getParameters() as $oneParameter) { + if (empty($oneParameter->getAttributes(FunctionParameter::class))) { + var_dump('Method Has Public API attribute but parameter $'.$oneParameter->getName().' is undocumented '.$oneMethod->getDeclaringClass()->getName().'->'.$oneMethod->getName()); + } + } + } + + if (empty($oneMethod->getAttributes(Description::class)) && $oneMethod->getDeclaringClass()->getNamespaceName() !== '') { + var_dump('Description Attribute is empty: '.$oneMethod->getDeclaringClass()->getName().'->'.$oneMethod->getName()); + } + } else { + $non_inDoc++; + + if (empty($oneMethod->getAttributes(PublicAPI::class)) && $oneMethod->getDeclaringClass()->getNamespaceName() !== '') { + // var_dump('Method not has API attribute: '.$oneMethod->getDeclaringClass()->getName().'->'.$oneMethod->getName()); + } + } + } + } + + $full_methods_list = []; + + // generate .md + foreach ($FullClassList as $FullClass) { + $reflectionClass = new \ReflectionClass($FullClass); + $methods = ($reflectionClass)->getMethods(); + $shortClass = str_replace('CondorcetPHP\Condorcet\\', '', $FullClass); + + $full_methods_list[$shortClass] = [ + 'FullClass' => $FullClass, + 'shortClass' => $shortClass, + 'ReflectionClass' => $reflectionClass, + 'methods' => [], + ]; + + foreach ($methods as $oneMethod) { + $method_array = $full_methods_list[$shortClass]['methods'][$oneMethod->name] = [ + 'FullClass' => $FullClass, + 'shortClass' => $shortClass, + 'name' => $oneMethod->name, + 'static' => $oneMethod->isStatic(), + 'visibility_public' => $oneMethod->isPublic(), + 'visibility_protected' => $oneMethod->isProtected(), + 'visibility_private' => $oneMethod->isPrivate(), + 'ReflectionMethod' => $oneMethod, + 'ReflectionClass' => $oneMethod->getDeclaringClass(), + ]; + + $total_methods++; + + if (!$oneMethod->isInternal()) { + $total_nonInternal_methods++; + } + + // Write Markdown + if (!empty($apiAttribute = $oneMethod->getAttributes(PublicAPI::class)) && (empty($apiAttribute[0]->getArguments()) || \in_array(self::simpleClass($oneMethod->class), $apiAttribute[0]->getArguments(), true))) { + $path = $pathDirectory . str_replace('\\', '_', self::simpleClass($oneMethod->class)) . ' Class/'; + + if (!is_dir($path)) { + mkdir($path); + } + + file_put_contents($path.self::makeFilename($oneMethod), $this->createMarkdownContent($oneMethod, $method_array)); + } + } + } + + + print 'Public methods in doc: '.$inDoc.' / '.($inDoc + $non_inDoc).' | Total non-internal methods count: '.$total_nonInternal_methods.' | Number of Class: '.\count($FullClassList).' | Number of Methods including internals: '.$total_methods."\n"; + + // Add Index + $file_content = "> **[Presentation](../README.md) | [Manual](https://github.com/julien-boudry/Condorcet/wiki) | Methods References | [Tests](../Tests)**\n\n". + + "# Public API Index*_\n". + + "_*: I try to update and complete the documentation. See also [the manual](https://github.com/julien-boudry/Condorcet/wiki), [the tests](../Tests) also produce many examples. And create issues for questions or fixing documentation!_\n\n"; + + + $file_content .= $this->makeIndex($full_methods_list); + + $file_content .= "\n\n\n"; + + uksort($full_methods_list, 'strnatcmp'); + $file_content .= "# Full Class & Methods References\n". + "_Including above methods from public API_\n\n"; + + $file_content .= $this->makeProfundis($full_methods_list); + + + // Write file + file_put_contents($pathDirectory.'README.md', $file_content); + + + echo 'YAH !
' . (microtime(true) - $start_time) .'s'; + } + + + // Build Methods + + protected function createMarkdownContent(\ReflectionMethod $method, array $entry): string + { + // Header + $md = '## '.self::getModifiersName($method).' '. self::simpleClass($method->class).'::'.$method->name."\n\n". + "### Description \n\n". + self::computeRepresentationAsPHP($method)."\n\n". + $method->getAttributes(Description::class)[0]->getArguments()[0]."\n "; + + // Input + if ($method->getNumberOfParameters() > 0) { + foreach ($method->getParameters() as $key => $value) { + if (!empty($attributes = $value->getAttributes(FunctionParameter::class))) { + $pt = $attributes[0]->newInstance()->text; + } elseif (isset($entry['input'][$value->getName()]['text'])) { + $pt = $entry['input'][$value->getName()]['text']; + } else { + $pt = ''; + } + + $md .= "\n\n". + '##### **'.$value->getName().':** *'.self::getTypeAsString($value->getType(), true)."* \n". + $pt." \n"; + } + } + + + // Return Value + + if (!empty($method->getAttributes(FunctionReturn::class))) { + $md .= "\n\n". + "### Return value: \n\n". + '*('.self::getTypeAsString($method->getReturnType(), true).')* '.$method->getAttributes(FunctionReturn::class)[0]->getArguments()[0]."\n\n"; + } + + // Throw + if (!empty($method->getAttributes(Throws::class))) { + $md .= "\n\n". + "### Throws: \n\n"; + + foreach ($method->getAttributes(Throws::class)[0]->getArguments() as $arg) { + $md .= '* ```'.$arg."```\n"; + } + } + + // Related methods + + if (!empty($method->getAttributes(Related::class))) { + $md .= "\n". + "---------------------------------------\n\n". + "### Related method(s) \n\n"; + + foreach ($method->getAttributes(Related::class) as $RelatedAttribute) { + foreach ($RelatedAttribute->newInstance()->relatedList as $value) { + if ($value === self::simpleClass($method->class).'::'.$method->name) { + continue; + } + + $md .= '* '.self::cleverRelated($value)." \n"; + } + } + } + + if (!empty($method->getAttributes(Example::class))) { + $md .= "\n". + "---------------------------------------\n\n". + "### Examples and explanation\n\n"; + + foreach ($method->getAttributes(Example::class) as $ExampleAttribute) { + $ExampleAttribute = $ExampleAttribute->newInstance(); + + $md .= '* **['.$ExampleAttribute->name.']('.$ExampleAttribute->link.")** \n"; + } + } + + return $md; + } + + protected function makeIndex(array $index): string + { + $file_content = ''; + + $testPublicAttribute = static function (\ReflectionMethod $reflectionMethod): bool { + return !(empty($apiAttribute = $reflectionMethod->getAttributes(PublicAPI::class)) || (!empty($apiAttribute[0]->getArguments()) && !\in_array(self::simpleClass($reflectionMethod->class), $apiAttribute[0]->getArguments(), true))); + }; + + foreach ($index as $class => &$classMeta) { + usort($classMeta['methods'], static function (array $a, array $b): int { + if ($a['ReflectionMethod']->isStatic() === $b['ReflectionMethod']->isStatic()) { + return strnatcmp($a['ReflectionMethod']->name, $b['ReflectionMethod']->name); + } elseif ($a['ReflectionMethod']->isStatic() && !$b['ReflectionMethod']->isStatic()) { + return -1; + } else { + return 1; + } + }); + + $classWillBePublic = false; + + if ($classMeta['ReflectionClass']->getAttributes(PublicAPI::class)) { + $classWillBePublic = true; + } else { + foreach ($classMeta['methods'] as $oneMethod) { + if ($testPublicAttribute($oneMethod['ReflectionMethod'])) { + $classWillBePublic = true; + break; + } + } + + foreach ($classMeta['ReflectionClass']->getReflectionConstants() as $oneConstant) { + if (!empty($oneConstant->getAttributes(PublicAPI::class))) { + $classWillBePublic = true; + break; + } + } + + foreach ($classMeta['ReflectionClass']->getProperties() as $onePropertie) { + if (!empty($onePropertie->getAttributes(PublicAPI::class))) { + $classWillBePublic = true; + break; + } + } + } + + if ($classWillBePublic) { + $isEnum = enum_exists(($enumCases = $classMeta['ReflectionClass'])->name); + + $file_content .= "\n"; + $file_content .= '### CondorcetPHP\Condorcet\\'.$class.' '.((!$isEnum) ? 'Class' : 'Enum')." \n\n"; + + if ($isEnum) { + $file_content .= $this->makeEnumeCases(new \ReflectionEnum($enumCases->name), false); + $file_content .= "\n"; + } else { + $file_content .= $this->makeConstants($classMeta['ReflectionClass'], \ReflectionClassConstant::IS_PUBLIC, true); + } + + $file_content .= $this->makeProperties($classMeta['ReflectionClass'], null, true); + } + + + foreach ($classMeta['methods'] as $oneMethod) { + if (!$testPublicAttribute($oneMethod['ReflectionMethod']) || !$oneMethod['ReflectionMethod']->isUserDefined()) { + continue; + } else { + $url = str_replace('\\', '_', self::simpleClass($oneMethod['ReflectionMethod']->class)).' Class/'.self::getModifiersName($oneMethod['ReflectionMethod']).' '. str_replace('\\', '_', self::simpleClass($oneMethod['ReflectionMethod']->class).'--'. $oneMethod['ReflectionMethod']->name) . '.md'; + $url = str_replace(' ', '%20', $url); + + $file_content .= '* ['.self::computeRepresentationAsForIndex($oneMethod['ReflectionMethod']).']('.$url.')'; + + if (isset($oneMethod['ReflectionMethod']) && $oneMethod['ReflectionMethod']->hasReturnType()) { + $file_content .= ': '.self::getTypeAsString($oneMethod['ReflectionMethod']->getReturnType(), true); + } + + $file_content .= " \n"; + } + } + } + + return $file_content; + } + + protected function makeEnumeCases(\ReflectionEnum $enumReflection, bool $shortName = false): string + { + $cases = $enumReflection->getCases(); + + $r = ''; + + foreach ($cases as $oneCase) { + $name = ($shortName) ? $enumReflection->getShortName() : self::simpleClass($enumReflection->getName()); + $r .= '* ```case '.$name.'::'.$oneCase->getName()."``` \n"; + } + + return $r; + } + + protected function makeConstants(\ReflectionClass $class, ?int $type = null, bool $mustHaveApiAttribute = false, bool $addMdCodeTag = true): string + { + $file_content = ''; + + $hasConstants = false; + + foreach ($class->getReflectionConstants($type) as $constant) { + if (!$mustHaveApiAttribute || !empty($constant->getAttributes(PublicAPI::class))) { + $file_content .= '* '; + $file_content .= $addMdCodeTag ? '```' : ''; + + $file_content .= $constant->isFinal() ? 'final ' : ''; + + $file_content .= $constant->isPublic() ? 'public' : ''; + $file_content .= $constant->isProtected() ? 'protected' : ''; + $file_content .= $constant->isPrivate() ? 'private' : ''; + + $file_content .= ' const '.$constant->getName().': ('.\gettype($constant->getValue()).')'; + $file_content .= $addMdCodeTag ? '``` ' : ''; + $file_content .= "\n"; + $hasConstants = true; + } + } + + if ($hasConstants) { + $file_content .= "\n"; + } + + return $file_content; + } + + protected function makeProperties(\ReflectionClass $class, ?int $type = null, bool $mustHaveApiAttribute = false, bool $addMdCodeTag = true): string + { + $file_content = ''; + + $hasConstants = false; + + foreach ($class->getProperties($type) as $propertie) { + if (!$mustHaveApiAttribute || !empty($propertie->getAttributes(PublicAPI::class))) { + $file_content .= '* '; + $file_content .= $addMdCodeTag ? '```' : ''; + + $file_content .= $propertie->isReadOnly() ? 'readonly ' : ''; + + $file_content .= $propertie->isPublic() ? 'public ' : ''; + $file_content .= $propertie->isProtected() ? 'protected ' : ''; + $file_content .= $propertie->isPrivate() ? 'private ' : ''; + + $file_content .= $propertie->isStatic() ? 'static ' : ''; + + $file_content .= ((string) $propertie->getType()).' $'.$propertie->getName(); + $file_content .= $addMdCodeTag ? '``` ' : ''; + $file_content .= "\n"; + $hasConstants = true; + } + } + + if ($hasConstants) { + $file_content .= "\n"; + } + + return $file_content; + } + + protected function makeProfundis(array $index): string + { + $file_content = ''; + + foreach ($index as $class => &$classMeta) { + usort($classMeta['methods'], static function (array $a, array $b): int { + if ($a['static'] === $b['static']) { + if ($a['visibility_public'] && !$b['visibility_public']) { + return -1; + } elseif (!$a['visibility_public'] && $b['visibility_public']) { + return 1; + } else { + if ($a['visibility_protected'] && !$b['visibility_protected']) { + return -1; + } elseif (!$a['visibility_protected'] && $b['visibility_protected']) { + return 1; + } else { + return strnatcmp($a['name'], $b['name']); + } + } + } elseif ($a['static'] && !$b['static']) { + return -1; + } else { + return 1; + } + }); + + $file_content .= "\n"; + $file_content .= '#### '; + $file_content .= ($classMeta['ReflectionClass']->isAbstract()) ? 'Abstract ' : ''; + $file_content .= 'CondorcetPHP\Condorcet\\'.$class.' '; + + $file_content .= ($p = $classMeta['ReflectionClass']->getParentClass()) ? 'extends '.$p->name.' ' : ''; + + $interfaces = implode(', ', $classMeta['ReflectionClass']->getInterfaceNames()); + $file_content .= (!empty($interfaces)) ? 'implements '.$interfaces : ''; + + $file_content .= " \n"; + $file_content .= "```php\n"; + + $isEnum = enum_exists(($enumCases = $classMeta['ReflectionClass'])->name); + + if ($isEnum) { + $file_content .= $this->makeEnumeCases(new \ReflectionEnum($enumCases->name), true); + $file_content .= "\n"; + } else { + $file_content .= $this->makeConstants(class: $classMeta['ReflectionClass'], addMdCodeTag: false); + } + + $file_content .= $this->makeProperties(class: $classMeta['ReflectionClass'], addMdCodeTag: false); + + foreach ($classMeta['methods'] as $oneMethod) { + if ($oneMethod['ReflectionMethod']->isUserDefined()) { + $parameters = $oneMethod['ReflectionMethod']->getParameters(); + $parameters_string = ''; + + $i = 0; + foreach ($parameters as $oneP) { + $parameters_string .= (++$i > 1) ? ', ' : ''; + + if ($oneP->getType() !== null) { + $parameters_string .= self::getTypeAsString($oneP->getType()) . ' '; + } + $parameters_string .= '$'.$oneP->name; + + if ($oneP->isDefaultValueAvailable()) { + $parameters_string .= ' = '.self::speakBool($oneP->getDefaultValue()); + } + } + + $representation = ($oneMethod['visibility_public']) ? 'public ' : ''; + $representation .= ($oneMethod['visibility_protected']) ? 'protected ' : ''; + $representation .= ($oneMethod['visibility_private']) ? 'private ' : ''; + + $representation .= ($oneMethod['static']) ? 'static ' : ''; + $representation .= $oneMethod['name'] . ' ('.$parameters_string.')'; + + if ($oneMethod['ReflectionMethod']->hasReturnType()) { + $representation .= ': '.self::getTypeAsString($oneMethod['ReflectionMethod']->getReturnType()); + } + + $file_content .= '* '.$representation." \n"; + } + } + + $file_content .= "```\n"; + } + + return $file_content; + } +} diff --git a/include/Condorcet/Dev/Dockerfile.infection b/include/Condorcet/Dev/Dockerfile.infection new file mode 100644 index 0000000..666d482 --- /dev/null +++ b/include/Condorcet/Dev/Dockerfile.infection @@ -0,0 +1,14 @@ +FROM condorcet + +RUN pecl install pcov \ + && docker-php-ext-enable pcov + +RUN php composer.phar install --optimize-autoloader --no-progress \ + && rm -rf /root/.composer/cache + +ENTRYPOINT [ "vendor/bin/infection" ] + +# Usage: +# 1. docker build -f Dockerfile.infection -t infection . +# 2. docker run --hostname="infection" --mount type=bind,src=$(pwd),dst=/usr/src/condorcetapp --rm -it infection:latest +# The infection test takes a long time. You can use --filter to only test a portion of the code. diff --git a/include/Condorcet/Dev/Get_Some_Codes_Stats.md b/include/Condorcet/Dev/Get_Some_Codes_Stats.md new file mode 100644 index 0000000..b005b8f --- /dev/null +++ b/include/Condorcet/Dev/Get_Some_Codes_Stats.md @@ -0,0 +1,6 @@ +Get some codes stats: + +``` +vendor/bin/phploc src Benchmarks Dev Tests Examples # With Test and Examples +vendor/bin/phploc src Benchmarks Dev # Without Tests and Examples +``` \ No newline at end of file diff --git a/include/Condorcet/Dev/Infection.md b/include/Condorcet/Dev/Infection.md new file mode 100644 index 0000000..386d974 --- /dev/null +++ b/include/Condorcet/Dev/Infection.md @@ -0,0 +1,8 @@ +To generate Infection report: + +_Customize with the right number of threads for your system._ +``` +php vendor/bin/infection --threads=24 +``` + +_Logs at the racine, infection.log_ \ No newline at end of file diff --git a/include/Condorcet/Dev/InfectionScoreHistory/Infection-Score_History.md b/include/Condorcet/Dev/InfectionScoreHistory/Infection-Score_History.md new file mode 100644 index 0000000..4aba588 --- /dev/null +++ b/include/Condorcet/Dev/InfectionScoreHistory/Infection-Score_History.md @@ -0,0 +1,83 @@ +2021-01-03 - > 7590291d + + 2479 mutations were generated: + 1872 mutants were killed + 14 mutants were not covered by tests + 427 covered mutants were not detected + 110 errors were encountered + 0 syntax errors were encountered + 56 time outs were encountered + 0 mutants required more time than configured + + Metrics: + Mutation Score Indicator (MSI): 82% + Mutation Code Coverage: 99% + Covered Code MSI: 82% + +2021-09-28 - 9882d9c + + 2538 mutations were generated: + 1879 mutants were killed + 14 mutants were not covered by tests + 479 covered mutants were not detected + 110 errors were encountered + 0 syntax errors were encountered + 56 time outs were encountered + 0 mutants required more time than configured + + Metrics: + Mutation Score Indicator (MSI): 80% + Mutation Code Coverage: 99% + Covered Code MSI: 81% + + +2021-09-20 - 24d4ba4 + + 2586 mutations were generated: + 1892 mutants were killed + 42 mutants were not covered by tests + 479 covered mutants were not detected + 117 errors were encountered + 0 syntax errors were encountered + 56 time outs were encountered + 0 mutants required more time than configured + + Metrics: + Mutation Score Indicator (MSI): 79% + Mutation Code Coverage: 98% + Covered Code MSI: 81% + + +2021-08-05 - 40bfb78 + + 2685 mutations were generated: + 1962 mutants were killed + 53 mutants were not covered by tests + 492 covered mutants were not detected + 122 errors were encountered + 0 syntax errors were encountered + 56 time outs were encountered + 0 mutants required more time than configured + + Metrics: + Mutation Score Indicator (MSI): 79% + Mutation Code Coverage: 98% + Covered Code MSI: 81% + + +2022-07-01 - 39b9c31 + + 3207 mutations were generated: + 1229 mutants were killed + 0 mutants were configured to be ignored + 42 mutants were not covered by tests + 191 covered mutants were not detected + 1 errors were encountered + 0 syntax errors were encountered + 11 time outs were encountered + 1733 mutants required more time than configured + + Metrics: + Mutation Score Indicator (MSI): 84% + Mutation Code Coverage: 97% + Covered Code MSI: 86% \ No newline at end of file diff --git a/include/Condorcet/Dev/bugs/JitBug.php b/include/Condorcet/Dev/bugs/JitBug.php new file mode 100644 index 0000000..db56d1c --- /dev/null +++ b/include/Condorcet/Dev/bugs/JitBug.php @@ -0,0 +1,94 @@ +__construct() +// #1 C:\dev_scripts\Condorcet\lib\Algo\Methods\STV\SingleTransferableVote.php(91): CondorcetPHP\Condorcet\Algo\Method->createResult() +// #2 C:\dev_scripts\Condorcet\lib\Algo\Method.php(63): CondorcetPHP\Condorcet\Algo\Methods\STV\SingleTransferableVote->compute() +// #3 C:\dev_scripts\Condorcet\lib\ElectionProcess\ResultsProcess.php(80): CondorcetPHP\Condorcet\Algo\Method->getResult() +// #4 C:\dev_scripts\Condorcet\Dev\bugs\JitBug.php(24): CondorcetPHP\Condorcet\Election->getResult() +// #5 {main} +// thrown in C:\dev_scripts\Condorcet\lib\Result.php on line 89 + +# Test code + +namespace CondorcetPHP\Condorcet; + +require_once __DIR__.'/../../__CondorcetAutoload.php'; + +for ($i=1; $i <= 4000; $i++) { + + # With Condorcet + $election = new Election; + + $election->parseCandidates('A;B;C'); + + $election->addVote('A>B>C'); + + $election->getResult('Schulze'); + $election->getResult('STV'); + + # Tentative to reproduce (impossible...) + // $a = new A (new Bar); + // $b = new B (new Bar); +} + + +# Tentative to reproduce (impossible...) + +class Foo +{ + public function __construct(public string $fromMethod, public string $byClass, public Bar $election, public array $result, public $stats, public ?int $seats = null, public array $methodOptions = []) + { + } +} + +class Bar +{ +} + +abstract class Base +{ + public const IS_PROPORTIONAL = false; + + public Foo $foo; + + public function __construct(public Bar $barInstance) + { + $this->foo = new Foo( + fromMethod: 'test', + byClass: $this::class, + election: $this->barInstance, + result: [], + stats: [], + seats: (static::IS_PROPORTIONAL) ? $this->getSeats() : null, + methodOptions: [] + ); + } + + public function getSeats(): int + { + return 6; + } +} + +class A extends Base +{ + public const IS_PROPORTIONAL = false; +} + +class B extends Base +{ + public const IS_PROPORTIONAL = true; +} diff --git a/include/Condorcet/Dev/generate-large-vote-input_file.php b/include/Condorcet/Dev/generate-large-vote-input_file.php new file mode 100644 index 0000000..1320883 --- /dev/null +++ b/include/Condorcet/Dev/generate-large-vote-input_file.php @@ -0,0 +1,34 @@ +', $candidates)."\n"; + + if (mb_strlen($cache) > 5_000_000) { + $file->fwrite($cache); + $cache = ''; + } +} + +$file->fwrite($cache); diff --git a/include/Condorcet/Dev/generate_documentation_instructions.md b/include/Condorcet/Dev/generate_documentation_instructions.md new file mode 100644 index 0000000..49743db --- /dev/null +++ b/include/Condorcet/Dev/generate_documentation_instructions.md @@ -0,0 +1,5 @@ +To generate documentation: + +``` +php Dev/update-documentation.php +``` \ No newline at end of file diff --git a/include/Condorcet/Dev/interactiveShell.php b/include/Condorcet/Dev/interactiveShell.php new file mode 100644 index 0000000..70e32b4 --- /dev/null +++ b/include/Condorcet/Dev/interactiveShell.php @@ -0,0 +1,7 @@ + $ClassData) { + $path_to_file = [ + __DIR__.'/../lib/'.str_replace('\\', '/', $ClassName).'.php', + __DIR__.'/../lib/CondorcetVersion.php', + __DIR__.'/../lib/Linkable.php', + ]; + + if ($ClassName === 'Election') { + $path_to_file[] = __DIR__.'/../lib/ElectionProcess/CandidatesProcess.php'; + $path_to_file[] = __DIR__.'/../lib/ElectionProcess/VotesProcess.php'; + $path_to_file[] = __DIR__.'/../lib/ElectionProcess/ResultsProcess.php'; + } + + + + // var_dump($path_to_file); + + foreach ($path_to_file as $oneFile) { + $file_contents = file_get_contents($oneFile); + + foreach ($ClassData as $MethodName => $MethodData) { + var_dump($ClassName.'->'.$MethodName); + + $description = $MethodData['description']; + $description = str_replace('$', '\\\$', $description); + $description = preg_replace($patternReplaceLastNewLine, '$1', $description); + $description = str_replace(["\n ", "\n"], '\\n', $description); + $description = str_replace('"', '\"', $description); + + $attributes = '$3#[Description("'.$description.'")]$2'; + + if (isset($MethodData['return'])) { + $returnV = $MethodData['return']; + $returnV = preg_replace($patternReplaceLastNewLine, '$1', $returnV); + $returnV = str_replace(["\n ", "\n"], '\\n', $returnV); + $returnV = str_replace('"', '\"', $returnV); + + $attributes .= '$3#[FunctionReturn("'.$returnV.'")]$2'; + } + + + if (isset($MethodData['examples'])) { + $examples = $MethodData['examples']; + $examples = preg_replace($patternReplaceLastNewLine, '$1', $examples); + $examples = str_replace(["\n ", "\n"], '\\n', $examples); + $examples = str_replace('"', '\"', $examples); + + $arg = ''; + $i = 1; + foreach ($examples as $oneExampleKey => $oneExample) { + if ($i++ > 1) { + $arg .= ', '; + } + + $arg .= '"'.$oneExampleKey.'||'.$oneExample.'"'; + } + + $attributes .= '$3#[Examples('.$arg.')]$2'; + } + + if (isset($MethodData['related'])) { + $related = $MethodData['related']; + $related = str_replace('"', '\"', $related); + + $arg = ''; + $i = 1; + foreach ($related as $oneRelated) { + if ($i++ > 1) { + $arg .= ', '; + } + + $arg .= '"'.$oneRelated.'"'; + } + + $attributes .= '$3#[Related('.$arg.')]$2'; + } + + + $pattern = '/(#\[PublicAPI\])(\n)(.*)(public.*function '.$MethodName.' *\()/'; + $replacement = '$1$2'.$attributes.'$3$4'; + + $file_contents = preg_replace( + $pattern, + $replacement, + $file_contents + ); + } + + file_put_contents($oneFile, $file_contents); + } +} diff --git a/include/Condorcet/Dev/update-documentation.php b/include/Condorcet/Dev/update-documentation.php new file mode 100644 index 0000000..765073b --- /dev/null +++ b/include/Condorcet/Dev/update-documentation.php @@ -0,0 +1,40 @@ +B;C>A;B>A" -lr "Schulze" diff --git a/include/Condorcet/Documentation/Algo_Pairwise Class/public Algo_Pairwise--getExplicitPairwise.md b/include/Condorcet/Documentation/Algo_Pairwise Class/public Algo_Pairwise--getExplicitPairwise.md new file mode 100644 index 0000000..552fc68 --- /dev/null +++ b/include/Condorcet/Documentation/Algo_Pairwise Class/public Algo_Pairwise--getExplicitPairwise.md @@ -0,0 +1,22 @@ +## public Algo\Pairwise::getExplicitPairwise + +### Description + +```php +public Algo\Pairwise->getExplicitPairwise ( ): array +``` + +Return the Pairwise. + + +### Return value: + +*(```array```)* Pairwise as an explicit array . + + +--------------------------------------- + +### Related method(s) + +* [Election::getPairwise](../Election%20Class/public%20Election--getPairwise.md) +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) diff --git a/include/Condorcet/Documentation/Algo_Pairwise Class/public Algo_Pairwise--getObjectVersion.md b/include/Condorcet/Documentation/Algo_Pairwise Class/public Algo_Pairwise--getObjectVersion.md new file mode 100644 index 0000000..babe8a7 --- /dev/null +++ b/include/Condorcet/Documentation/Algo_Pairwise Class/public Algo_Pairwise--getObjectVersion.md @@ -0,0 +1,25 @@ +## public Algo\Pairwise::getObjectVersion + +### Description + +```php +public Algo\Pairwise->getObjectVersion ( [bool $major = false] ): string +``` + +Get the Condorcet PHP version who built this Election object. Usefull pour serializing Election. + + +##### **major:** *```bool```* +true will return : '2.0' and false will return : '2.0.0'. + + +### Return value: + +*(```string```)* Condorcet PHP version. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getVersion](../Condorcet%20Class/public%20static%20Condorcet--getVersion.md) diff --git a/include/Condorcet/Documentation/Algo_Tools_StvQuotas Class/public static Algo_Tools_StvQuotas--make.md b/include/Condorcet/Documentation/Algo_Tools_StvQuotas Class/public static Algo_Tools_StvQuotas--make.md new file mode 100644 index 0000000..55a7918 --- /dev/null +++ b/include/Condorcet/Documentation/Algo_Tools_StvQuotas Class/public static Algo_Tools_StvQuotas--make.md @@ -0,0 +1,25 @@ +## public static Algo\Tools\StvQuotas::make + +### Description + +```php +public static Algo\Tools\StvQuotas::make ( string $quota ): self +``` + +Build the Enum Quotas option for STV methods + + +##### **quota:** *```string```* +Quota name. + + +### Return value: + +*(```self```)* The Quota option + + +--------------------------------------- + +### Examples and explanation + +* **[Manual - STV method](https://github.com/julien-boudry/Condorcet/blob/master/VOTING_METHODS.md#single-transferable-vote)** diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--__construct.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--__construct.md new file mode 100644 index 0000000..e41329d --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--__construct.md @@ -0,0 +1,25 @@ +## public Candidate::__construct + +### Description + +```php +public Candidate->__construct ( string $name ) +``` + +Build a candidate. + + +##### **name:** *```string```* +Candidate Name. + +--------------------------------------- + +### Related method(s) + +* [Candidate::setName](../Candidate%20Class/public%20Candidate--setName.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Create Candidates](https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates)** diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--countLinks.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--countLinks.md new file mode 100644 index 0000000..d7e1255 --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--countLinks.md @@ -0,0 +1,25 @@ +## public Candidate::countLinks + +### Description + +```php +public Candidate->countLinks ( ): int +``` + +Count number of linked election to this object. + + +### Return value: + +*(```int```)* Number of linked elections. + + +--------------------------------------- + +### Related method(s) + +* [Vote::countLinks](../Vote%20Class/public%20Vote--countLinks.md) +* [Vote::getLinks](../Vote%20Class/public%20Vote--getLinks.md) +* [Candidate::getLinks](../Candidate%20Class/public%20Candidate--getLinks.md) +* [Vote::haveLink](../Vote%20Class/public%20Vote--haveLink.md) +* [Candidate::haveLink](../Candidate%20Class/public%20Candidate--haveLink.md) diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--getCreateTimestamp.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--getCreateTimestamp.md new file mode 100644 index 0000000..0d447bd --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--getCreateTimestamp.md @@ -0,0 +1,21 @@ +## public Candidate::getCreateTimestamp + +### Description + +```php +public Candidate->getCreateTimestamp ( ): float +``` + +Get the timestamp corresponding of the creation of this candidate. + + +### Return value: + +*(```float```)* Timestamp + + +--------------------------------------- + +### Related method(s) + +* [Candidate::getTimestamp](../Candidate%20Class/public%20Candidate--getTimestamp.md) diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--getHistory.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--getHistory.md new file mode 100644 index 0000000..1129eea --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--getHistory.md @@ -0,0 +1,21 @@ +## public Candidate::getHistory + +### Description + +```php +public Candidate->getHistory ( ): array +``` + +Return an history of each namming change, with timestamp. + + +### Return value: + +*(```array```)* An explicit multi-dimenssional array. + + +--------------------------------------- + +### Related method(s) + +* [Candidate::getCreateTimestamp](../Candidate%20Class/public%20Candidate--getCreateTimestamp.md) diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--getLinks.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--getLinks.md new file mode 100644 index 0000000..313944a --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--getLinks.md @@ -0,0 +1,25 @@ +## public Candidate::getLinks + +### Description + +```php +public Candidate->getLinks ( ): WeakMap +``` + +Get elections object linked to this Vote or Candidate object. + + +### Return value: + +*(```WeakMap```)* Populated by each elections Condorcet object. + + +--------------------------------------- + +### Related method(s) + +* [Vote::countLinks](../Vote%20Class/public%20Vote--countLinks.md) +* [Candidate::countLinks](../Candidate%20Class/public%20Candidate--countLinks.md) +* [Vote::getLinks](../Vote%20Class/public%20Vote--getLinks.md) +* [Vote::haveLink](../Vote%20Class/public%20Vote--haveLink.md) +* [Candidate::haveLink](../Candidate%20Class/public%20Candidate--haveLink.md) diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--getName.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--getName.md new file mode 100644 index 0000000..ffbc726 --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--getName.md @@ -0,0 +1,22 @@ +## public Candidate::getName + +### Description + +```php +public Candidate->getName ( ): string +``` + +Get the candidate name. + + +### Return value: + +*(```string```)* Candidate name. + + +--------------------------------------- + +### Related method(s) + +* [Candidate::getHistory](../Candidate%20Class/public%20Candidate--getHistory.md) +* [Candidate::setName](../Candidate%20Class/public%20Candidate--setName.md) diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--getObjectVersion.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--getObjectVersion.md new file mode 100644 index 0000000..e4f8a29 --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--getObjectVersion.md @@ -0,0 +1,25 @@ +## public Candidate::getObjectVersion + +### Description + +```php +public Candidate->getObjectVersion ( [bool $major = false] ): string +``` + +Get the Condorcet PHP version who built this Election object. Usefull pour serializing Election. + + +##### **major:** *```bool```* +true will return : '2.0' and false will return : '2.0.0'. + + +### Return value: + +*(```string```)* Condorcet PHP version. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getVersion](../Condorcet%20Class/public%20static%20Condorcet--getVersion.md) diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--getProvisionalState.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--getProvisionalState.md new file mode 100644 index 0000000..9ba52a0 --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--getProvisionalState.md @@ -0,0 +1,19 @@ +## public Candidate::getProvisionalState + +### Description + +```php +public Candidate->getProvisionalState ( ): bool +``` + +When you create yourself the vote object, without use the Election::addVote or other native election method. And if you use string input (or array of string). +Then, these string input will be converted to into temporary candidate objects, named "provisional". because you don't create the candidate yourself. They have a provisonal statut true. +When the vote will be added for the first time to an election, provisional candidate object with a name that matches an election candidate, will be converted into the election candidate. And first ranking will be save into Vote history (Vote::getHistory). + +See VoteTest::testVoteHistory() test for a demonstration. In principle this is transparent from a usage point of view. If you want to avoid any non-strict comparisons, however, you should prefer to create your votes with the Election object, or with Candidate Objects in input. But, you must never getback a candidate marked as provisional in an another election in the same time, it's will not working. + + +### Return value: + +*(```bool```)* True if candidate object is in a provisional state, false else. + diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--getTimestamp.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--getTimestamp.md new file mode 100644 index 0000000..88a83d8 --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--getTimestamp.md @@ -0,0 +1,21 @@ +## public Candidate::getTimestamp + +### Description + +```php +public Candidate->getTimestamp ( ): float +``` + +Get the timestamp corresponding of the last namming change. + + +### Return value: + +*(```float```)* Timestamp + + +--------------------------------------- + +### Related method(s) + +* [Candidate::getCreateTimestamp](../Candidate%20Class/public%20Candidate--getCreateTimestamp.md) diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--haveLink.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--haveLink.md new file mode 100644 index 0000000..62498da --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--haveLink.md @@ -0,0 +1,29 @@ +## public Candidate::haveLink + +### Description + +```php +public Candidate->haveLink ( CondorcetPHP\Condorcet\Election $election ): bool +``` + +Check if this election is linked with this Candidate/Vote object. + + +##### **election:** *```CondorcetPHP\Condorcet\Election```* +Condorcet election to check. + + +### Return value: + +*(```bool```)* True or False. + + +--------------------------------------- + +### Related method(s) + +* [Vote::countLinks](../Vote%20Class/public%20Vote--countLinks.md) +* [Candidate::countLinks](../Candidate%20Class/public%20Candidate--countLinks.md) +* [Vote::getLinks](../Vote%20Class/public%20Vote--getLinks.md) +* [Candidate::getLinks](../Candidate%20Class/public%20Candidate--getLinks.md) +* [Vote::haveLink](../Vote%20Class/public%20Vote--haveLink.md) diff --git a/include/Condorcet/Documentation/Candidate Class/public Candidate--setName.md b/include/Condorcet/Documentation/Candidate Class/public Candidate--setName.md new file mode 100644 index 0000000..cd32f1f --- /dev/null +++ b/include/Condorcet/Documentation/Candidate Class/public Candidate--setName.md @@ -0,0 +1,25 @@ +## public Candidate::setName + +### Description + +```php +public Candidate->setName ( string $name ): bool +``` + +Change the candidate name. +*If this will not cause conflicts if the candidate is already participating in elections and would namesake. This situation will throw an exception.* + + +##### **name:** *```string```* +Candidate Name. + + +### Return value: + +*(```bool```)* In case of success, return TRUE + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\CandidateInvalidNameException``` diff --git a/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--addMethod.md b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--addMethod.md new file mode 100644 index 0000000..89120b9 --- /dev/null +++ b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--addMethod.md @@ -0,0 +1,26 @@ +## public static Condorcet::addMethod + +### Description + +```php +public static Condorcet::addMethod ( string $methodClass ): bool +``` + +If you create your own Condorcet Algo. You will need it ! + + +##### **methodClass:** *```string```* +The class name implementing your method. The class name includes the namespace it was declared in (e.g. Foo\Bar). + + +### Return value: + +*(```bool```)* True on Success. False on failure. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::isAuthMethod](../Condorcet%20Class/public%20static%20Condorcet--isAuthMethod.md) +* [static Condorcet::getMethodClass](../Condorcet%20Class/public%20static%20Condorcet--getMethodClass.md) diff --git a/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getAuthMethods.md b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getAuthMethods.md new file mode 100644 index 0000000..6b01088 --- /dev/null +++ b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getAuthMethods.md @@ -0,0 +1,26 @@ +## public static Condorcet::getAuthMethods + +### Description + +```php +public static Condorcet::getAuthMethods ( [bool $basic = false] ): array +``` + +Get a list of supported algorithm. + + +##### **basic:** *```bool```* +Include or not the natural Condorcet base algorithm. + + +### Return value: + +*(```array```)* Populated by method string name. You can use it on getResult ... and others methods. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::isAuthMethod](../Condorcet%20Class/public%20static%20Condorcet--isAuthMethod.md) +* [static Condorcet::getMethodClass](../Condorcet%20Class/public%20static%20Condorcet--getMethodClass.md) diff --git a/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getDefaultMethod.md b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getDefaultMethod.md new file mode 100644 index 0000000..e77be0c --- /dev/null +++ b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getDefaultMethod.md @@ -0,0 +1,22 @@ +## public static Condorcet::getDefaultMethod + +### Description + +```php +public static Condorcet::getDefaultMethod ( ): ?string +``` + +Return the Condorcet static default method. + + +### Return value: + +*(```?string```)* Method name. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getAuthMethods](../Condorcet%20Class/public%20static%20Condorcet--getAuthMethods.md) +* [static Condorcet::setDefaultMethod](../Condorcet%20Class/public%20static%20Condorcet--setDefaultMethod.md) diff --git a/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getMethodClass.md b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getMethodClass.md new file mode 100644 index 0000000..1346f1c --- /dev/null +++ b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getMethodClass.md @@ -0,0 +1,30 @@ +## public static Condorcet::getMethodClass + +### Description + +```php +public static Condorcet::getMethodClass ( string $method ): ?string +``` + +Return the full class path for a method. + + +##### **method:** *```string```* +A valid method name. + + +### Return value: + +*(```?string```)* Return null is method not exist. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\AlgorithmException``` + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getAuthMethods](../Condorcet%20Class/public%20static%20Condorcet--getAuthMethods.md) diff --git a/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getVersion.md b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getVersion.md new file mode 100644 index 0000000..5240281 --- /dev/null +++ b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--getVersion.md @@ -0,0 +1,26 @@ +## public static Condorcet::getVersion + +### Description + +```php +public static Condorcet::getVersion ( [bool $major = false] ): string +``` + +Get the library version. + + +##### **major:** *```bool```* +* true will return : '2.0' +* false will return : '2.0.0'. + + +### Return value: + +*(```string```)* Condorcet PHP version. + + +--------------------------------------- + +### Related method(s) + +* [Election::getObjectVersion](../Election%20Class/public%20Election--getObjectVersion.md) diff --git a/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--isAuthMethod.md b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--isAuthMethod.md new file mode 100644 index 0000000..6b302aa --- /dev/null +++ b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--isAuthMethod.md @@ -0,0 +1,26 @@ +## public static Condorcet::isAuthMethod + +### Description + +```php +public static Condorcet::isAuthMethod ( string $method ): bool +``` + +Test if a method is in the result set of Condorcet::getAuthMethods. + + +##### **method:** *```string```* +A valid method name or class. + + +### Return value: + +*(```bool```)* True / False + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getMethodClass](../Condorcet%20Class/public%20static%20Condorcet--getMethodClass.md) +* [static Condorcet::getAuthMethods](../Condorcet%20Class/public%20static%20Condorcet--getAuthMethods.md) diff --git a/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--setDefaultMethod.md b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--setDefaultMethod.md new file mode 100644 index 0000000..3702f99 --- /dev/null +++ b/include/Condorcet/Documentation/Condorcet Class/public static Condorcet--setDefaultMethod.md @@ -0,0 +1,25 @@ +## public static Condorcet::setDefaultMethod + +### Description + +```php +public static Condorcet::setDefaultMethod ( string $method ): bool +``` + +Put a new static method by default for the news Condorcet objects. + + +##### **method:** *```string```* +A valid method name or class. + + +### Return value: + +*(```bool```)* In case of success, return TRUE + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getDefaultMethod](../Condorcet%20Class/public%20static%20Condorcet--getDefaultMethod.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--__construct.md b/include/Condorcet/Documentation/Election Class/public Election--__construct.md new file mode 100644 index 0000000..db9437b --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--__construct.md @@ -0,0 +1,10 @@ +## public Election::__construct + +### Description + +```php +public Election->__construct ( ) +``` + +Build a new Election. + \ No newline at end of file diff --git a/include/Condorcet/Documentation/Election Class/public Election--addCandidate.md b/include/Condorcet/Documentation/Election Class/public Election--addCandidate.md new file mode 100644 index 0000000..72c59f1 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--addCandidate.md @@ -0,0 +1,41 @@ +## public Election::addCandidate + +### Description + +```php +public Election->addCandidate ( [CondorcetPHP\Condorcet\Candidate|string|null $candidate = null] ): CondorcetPHP\Condorcet\Candidate +``` + +Add one candidate to an election. + + +##### **candidate:** *```CondorcetPHP\Condorcet\Candidate|string|null```* +Alphanumeric string or CondorcetPHP\Condorcet\Candidate object. The whitespace of your candidate name will be trimmed. If null, this function will create a new candidate with an automatic name. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Candidate```)* The new candidate object (your or automatic one). Throws an exception on error (existing candidate...). + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\CandidateExistsException``` +* ```CondorcetPHP\Condorcet\Throwable\VotingHasStartedException``` + +--------------------------------------- + +### Related method(s) + +* [Election::parseCandidates](../Election%20Class/public%20Election--parseCandidates.md) +* [Election::addCandidatesFromJson](../Election%20Class/public%20Election--addCandidatesFromJson.md) +* [Election::removeCandidate](../Election%20Class/public%20Election--removeCandidate.md) +* [Election::getCandidatesList](../Election%20Class/public%20Election--getCandidatesList.md) +* [Election::canAddCandidate](../Election%20Class/public%20Election--canAddCandidate.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Manage Candidate](https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--addCandidatesFromJson.md b/include/Condorcet/Documentation/Election Class/public Election--addCandidatesFromJson.md new file mode 100644 index 0000000..6f54dae --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--addCandidatesFromJson.md @@ -0,0 +1,38 @@ +## public Election::addCandidatesFromJson + +### Description + +```php +public Election->addCandidatesFromJson ( string $input ): array +``` + +Import candidate from a JSON source. + + +##### **input:** *```string```* +JSON string input. + + +### Return value: + +*(```array```)* List of newly registered candidate object. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\CandidateExistsException``` + +--------------------------------------- + +### Related method(s) + +* [Election::addCandidate](../Election%20Class/public%20Election--addCandidate.md) +* [Election::parseCandidates](../Election%20Class/public%20Election--parseCandidates.md) +* [Election::addVotesFromJson](../Election%20Class/public%20Election--addVotesFromJson.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Manage Candidates](https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--addConstraint.md b/include/Condorcet/Documentation/Election Class/public Election--addConstraint.md new file mode 100644 index 0000000..d2d6870 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--addConstraint.md @@ -0,0 +1,38 @@ +## public Election::addConstraint + +### Description + +```php +public Election->addConstraint ( string $constraintClass ): bool +``` + +Add a constraint rules as a valid class path. + + +##### **constraintClass:** *```string```* +A valid class path. Class must extend VoteConstraint class. + + +### Return value: + +*(```bool```)* True on success. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\VoteConstraintException``` + +--------------------------------------- + +### Related method(s) + +* [Election::getConstraints](../Election%20Class/public%20Election--getConstraints.md) +* [Election::clearConstraints](../Election%20Class/public%20Election--clearConstraints.md) +* [Election::testIfVoteIsValidUnderElectionConstraints](../Election%20Class/public%20Election--testIfVoteIsValidUnderElectionConstraints.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Vote Constraints](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-5.-Vote-Constraints)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--addVote.md b/include/Condorcet/Documentation/Election Class/public Election--addVote.md new file mode 100644 index 0000000..dd9164a --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--addVote.md @@ -0,0 +1,43 @@ +## public Election::addVote + +### Description + +```php +public Election->addVote ( CondorcetPHP\Condorcet\Vote|array|string $vote [, array|string|null $tags = null] ): CondorcetPHP\Condorcet\Vote +``` + +Add a vote to an election. + + +##### **vote:** *```CondorcetPHP\Condorcet\Vote|array|string```* +String or array representation. Or CondorcetPHP\Condorcet\Vote object. If you not provide yourself Vote object, a new one will be generate for you. + + +##### **tags:** *```array|string|null```* +String separated by commas or an array. Will add tags to the vote object for you. But you can too add it yourself to Vote object. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Vote```)* The vote object. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\VoteMaxNumberReachedException``` + +--------------------------------------- + +### Related method(s) + +* [Election::parseVotes](../Election%20Class/public%20Election--parseVotes.md) +* [Election::addVotesFromJson](../Election%20Class/public%20Election--addVotesFromJson.md) +* [Election::removeVote](../Election%20Class/public%20Election--removeVote.md) +* [Election::getVotesList](../Election%20Class/public%20Election--getVotesList.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Vote Management](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--addVotesFromJson.md b/include/Condorcet/Documentation/Election Class/public Election--addVotesFromJson.md new file mode 100644 index 0000000..7694434 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--addVotesFromJson.md @@ -0,0 +1,33 @@ +## public Election::addVotesFromJson + +### Description + +```php +public Election->addVotesFromJson ( string $input ): int +``` + +Import votes from a Json source. + + +##### **input:** *```string```* +Json string input. + + +### Return value: + +*(```int```)* Count of new registered vote. + + +--------------------------------------- + +### Related method(s) + +* [Election::addVote](../Election%20Class/public%20Election--addVote.md) +* [Election::parseVotes](../Election%20Class/public%20Election--parseVotes.md) +* [Election::addCandidatesFromJson](../Election%20Class/public%20Election--addCandidatesFromJson.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Add Vote](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--allowsVoteWeight.md b/include/Condorcet/Documentation/Election Class/public Election--allowsVoteWeight.md new file mode 100644 index 0000000..d9a5e4d --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--allowsVoteWeight.md @@ -0,0 +1,27 @@ +## public Election::allowsVoteWeight + +### Description + +```php +public Election->allowsVoteWeight ( [bool $rule = true] ): bool +``` + +Set the setting and reset all result data. +Then the weight of votes (if specified) will be taken into account when calculating the results. Otherwise all votes will be considered equal. +By default, the voting weight is not activated and all votes are considered equal. + + +##### **rule:** *```bool```* +New rule. + + +### Return value: + +*(```bool```)* Return True + + +--------------------------------------- + +### Related method(s) + +* [Election::isVoteWeightAllowed](../Election%20Class/public%20Election--isVoteWeightAllowed.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--canAddCandidate.md b/include/Condorcet/Documentation/Election Class/public Election--canAddCandidate.md new file mode 100644 index 0000000..12e5284 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--canAddCandidate.md @@ -0,0 +1,25 @@ +## public Election::canAddCandidate + +### Description + +```php +public Election->canAddCandidate ( CondorcetPHP\Condorcet\Candidate|string $candidate ): bool +``` + +Check if a candidate is already registered. Uses strict Vote object comparison, but also string naming comparison in the election. + + +##### **candidate:** *```CondorcetPHP\Condorcet\Candidate|string```* +String or Condorcet/Vote object. + + +### Return value: + +*(```bool```)* True if your candidate is available, false otherwise. + + +--------------------------------------- + +### Related method(s) + +* [Election::addCandidate](../Election%20Class/public%20Election--addCandidate.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--clearConstraints.md b/include/Condorcet/Documentation/Election Class/public Election--clearConstraints.md new file mode 100644 index 0000000..c8166a5 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--clearConstraints.md @@ -0,0 +1,29 @@ +## public Election::clearConstraints + +### Description + +```php +public Election->clearConstraints ( ): bool +``` + +Clear all constraints rules and clear previous results. + + +### Return value: + +*(```bool```)* Return True. + + +--------------------------------------- + +### Related method(s) + +* [Election::getConstraints](../Election%20Class/public%20Election--getConstraints.md) +* [Election::addConstraints](../Election%20Class/public%20Election--addConstraints.md) +* [Election::testIfVoteIsValidUnderElectionConstraints](../Election%20Class/public%20Election--testIfVoteIsValidUnderElectionConstraints.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Vote Constraints](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-5.-Vote-Constraints)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--computeResult.md b/include/Condorcet/Documentation/Election Class/public Election--computeResult.md new file mode 100644 index 0000000..d22dc6d --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--computeResult.md @@ -0,0 +1,21 @@ +## public Election::computeResult + +### Description + +```php +public Election->computeResult ( [?string $method = null] ): void +``` + +Really similar to Election::getResult() but not return anything. Just calculates silently and fill the cache. + + +##### **method:** *```?string```* +Not requiered for use object default method. Set the string name of the algorithm for use an specific one. + +--------------------------------------- + +### Related method(s) + +* [Election::getWinner](../Election%20Class/public%20Election--getWinner.md) +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) +* [Condorcet::getDefaultMethod](../Condorcet%20Class/public%20Condorcet--getDefaultMethod.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--countCandidates.md b/include/Condorcet/Documentation/Election Class/public Election--countCandidates.md new file mode 100644 index 0000000..af396bd --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--countCandidates.md @@ -0,0 +1,21 @@ +## public Election::countCandidates + +### Description + +```php +public Election->countCandidates ( ): int +``` + +Count the number of registered candidates + + +### Return value: + +*(```int```)* Number of registered candidates for this election. + + +--------------------------------------- + +### Related method(s) + +* [Election::getCandidatesList](../Election%20Class/public%20Election--getCandidatesList.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--countInvalidVoteWithConstraints.md b/include/Condorcet/Documentation/Election Class/public Election--countInvalidVoteWithConstraints.md new file mode 100644 index 0000000..8936206 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--countInvalidVoteWithConstraints.md @@ -0,0 +1,23 @@ +## public Election::countInvalidVoteWithConstraints + +### Description + +```php +public Election->countInvalidVoteWithConstraints ( ): int +``` + +Count the number of actual invalid (if constraints functionality is enabled) but registered vote for this election. + + +### Return value: + +*(```int```)* Number of valid and registered vote into this election. + + +--------------------------------------- + +### Related method(s) + +* [Election::countValidVoteWithConstraints](../Election%20Class/public%20Election--countValidVoteWithConstraints.md) +* [Election::countVotes](../Election%20Class/public%20Election--countVotes.md) +* [Election::sumValidVotesWeightWithConstraints](../Election%20Class/public%20Election--sumValidVotesWeightWithConstraints.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--countValidVoteWithConstraints.md b/include/Condorcet/Documentation/Election Class/public Election--countValidVoteWithConstraints.md new file mode 100644 index 0000000..69823d2 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--countValidVoteWithConstraints.md @@ -0,0 +1,23 @@ +## public Election::countValidVoteWithConstraints + +### Description + +```php +public Election->countValidVoteWithConstraints ( ): int +``` + +Count the number of actual registered and valid vote for this election. This method don't ignore votes constraints, only valid vote will be counted. + + +### Return value: + +*(```int```)* Number of valid and registered vote into this election. + + +--------------------------------------- + +### Related method(s) + +* [Election::countInvalidVoteWithConstraints](../Election%20Class/public%20Election--countInvalidVoteWithConstraints.md) +* [Election::countVotes](../Election%20Class/public%20Election--countVotes.md) +* [Election::sumValidVotesWeightWithConstraints](../Election%20Class/public%20Election--sumValidVotesWeightWithConstraints.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--countVotes.md b/include/Condorcet/Documentation/Election Class/public Election--countVotes.md new file mode 100644 index 0000000..c24fb40 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--countVotes.md @@ -0,0 +1,30 @@ +## public Election::countVotes + +### Description + +```php +public Election->countVotes ( [array|string|null $tags = null , bool $with = true] ): int +``` + +Count the number of actual registered and valid vote for this election. This method ignore votes constraints, only valid vote will be counted. + + +##### **tags:** *```array|string|null```* +Tag into string separated by commas, or an Array. + + +##### **with:** *```bool```* +Count Votes with this tag ou without this tag-. + + +### Return value: + +*(```int```)* Number of valid and registered vote into this election. + + +--------------------------------------- + +### Related method(s) + +* [Election::getVotesList](../Election%20Class/public%20Election--getVotesList.md) +* [Election::countValidVoteWithConstraints](../Election%20Class/public%20Election--countValidVoteWithConstraints.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getCandidateObjectFromName.md b/include/Condorcet/Documentation/Election Class/public Election--getCandidateObjectFromName.md new file mode 100644 index 0000000..65c0ed6 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getCandidateObjectFromName.md @@ -0,0 +1,19 @@ +## public Election::getCandidateObjectFromName + +### Description + +```php +public Election->getCandidateObjectFromName ( string $candidateName ): ?CondorcetPHP\Condorcet\Candidate +``` + +Find candidate object by string and return the candidate object. + + +##### **candidateName:** *```string```* +Candidate name. + + +### Return value: + +*(```?CondorcetPHP\Condorcet\Candidate```)* Candidate object + diff --git a/include/Condorcet/Documentation/Election Class/public Election--getCandidatesList.md b/include/Condorcet/Documentation/Election Class/public Election--getCandidatesList.md new file mode 100644 index 0000000..7d53212 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getCandidatesList.md @@ -0,0 +1,21 @@ +## public Election::getCandidatesList + +### Description + +```php +public Election->getCandidatesList ( ): array +``` + +Return a list of registered candidates for this election. + + +### Return value: + +*(```array```)* List of candidates in an array. + + +--------------------------------------- + +### Related method(s) + +* [Election::countCandidates](../Election%20Class/public%20Election--countCandidates.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getCandidatesListAsString.md b/include/Condorcet/Documentation/Election Class/public Election--getCandidatesListAsString.md new file mode 100644 index 0000000..aafe3bb --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getCandidatesListAsString.md @@ -0,0 +1,21 @@ +## public Election::getCandidatesListAsString + +### Description + +```php +public Election->getCandidatesListAsString ( ): array +``` + +Return a list of registered candidates for this election. + + +### Return value: + +*(```array```)* List of candidates in an array populated with strings instead of CandidateObjects. + + +--------------------------------------- + +### Related method(s) + +* [Election::countCandidates](../Election%20Class/public%20Election--countCandidates.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getChecksum.md b/include/Condorcet/Documentation/Election Class/public Election--getChecksum.md new file mode 100644 index 0000000..f0f2534 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getChecksum.md @@ -0,0 +1,27 @@ +## public Election::getChecksum + +### Description + +```php +public Election->getChecksum ( ): string +``` + +SHA-2 256 checksum of following internal data: +* Candidates +* Votes list & tags +* Computed data (pairwise, algorithm cache, stats) +* Class version (major version like 0.14) + +Can be powerfull to check integrity and security of an election. Or working with serialized object. + + +### Return value: + +*(```string```)* SHA-2 256 bits Hexadecimal + + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Cryptographic Checksum](https://github.com/julien-boudry/Condorcet/wiki/III-%23-A.-Avanced-features---Configuration-%23-2.-Cryptographic-Checksum)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getCondorcetLoser.md b/include/Condorcet/Documentation/Election Class/public Election--getCondorcetLoser.md new file mode 100644 index 0000000..3a340cf --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getCondorcetLoser.md @@ -0,0 +1,29 @@ +## public Election::getCondorcetLoser + +### Description + +```php +public Election->getCondorcetLoser ( ): ?CondorcetPHP\Condorcet\Candidate +``` + +Get the natural Condorcet loser if there is one. + + +### Return value: + +*(```?CondorcetPHP\Condorcet\Candidate```)* Candidate object given. Null if there are no available loser. + + +--------------------------------------- + +### Related method(s) + +* [Election::getCondorcetWinner](../Election%20Class/public%20Election--getCondorcetWinner.md) +* [Election::getLoser](../Election%20Class/public%20Election--getLoser.md) +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Natural Condorcet](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-1.-Natural-Condorcet)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getCondorcetWinner.md b/include/Condorcet/Documentation/Election Class/public Election--getCondorcetWinner.md new file mode 100644 index 0000000..2f95fdd --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getCondorcetWinner.md @@ -0,0 +1,29 @@ +## public Election::getCondorcetWinner + +### Description + +```php +public Election->getCondorcetWinner ( ): ?CondorcetPHP\Condorcet\Candidate +``` + +Get the natural Condorcet winner if there is one. + + +### Return value: + +*(```?CondorcetPHP\Condorcet\Candidate```)* Candidate object given. Null if there are no available winner. + + +--------------------------------------- + +### Related method(s) + +* [Election::getCondorcetLoser](../Election%20Class/public%20Election--getCondorcetLoser.md) +* [Election::getWiner](../Election%20Class/public%20Election--getWiner.md) +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Natural Condorcet](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-1.-Natural-Condorcet)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getConstraints.md b/include/Condorcet/Documentation/Election Class/public Election--getConstraints.md new file mode 100644 index 0000000..5f1fdbf --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getConstraints.md @@ -0,0 +1,29 @@ +## public Election::getConstraints + +### Description + +```php +public Election->getConstraints ( ): array +``` + +Get active constraints list. + + +### Return value: + +*(```array```)* Array with class name of each active constraint. Empty array if there is not. + + +--------------------------------------- + +### Related method(s) + +* [Election::clearConstraints](../Election%20Class/public%20Election--clearConstraints.md) +* [Election::addConstraints](../Election%20Class/public%20Election--addConstraints.md) +* [Election::testIfVoteIsValidUnderElectionConstraints](../Election%20Class/public%20Election--testIfVoteIsValidUnderElectionConstraints.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Vote Constraints](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-5.-Vote-Constraints)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getExplicitPairwise.md b/include/Condorcet/Documentation/Election Class/public Election--getExplicitPairwise.md new file mode 100644 index 0000000..c024db9 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getExplicitPairwise.md @@ -0,0 +1,22 @@ +## public Election::getExplicitPairwise + +### Description + +```php +public Election->getExplicitPairwise ( ): array +``` + +Return the Pairwise. + + +### Return value: + +*(```array```)* Pairwise as an explicit array . + + +--------------------------------------- + +### Related method(s) + +* [Election::getPairwise](../Election%20Class/public%20Election--getPairwise.md) +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getGlobalTimer.md b/include/Condorcet/Documentation/Election Class/public Election--getGlobalTimer.md new file mode 100644 index 0000000..bd18892 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getGlobalTimer.md @@ -0,0 +1,27 @@ +## public Election::getGlobalTimer + +### Description + +```php +public Election->getGlobalTimer ( ): float +``` + +Returns the cumulated computation runtime of this object. Include only computation related methods. + + +### Return value: + +*(```float```)* (Float) Timer + + +--------------------------------------- + +### Related method(s) + +* [Election::getLastTimer](../Election%20Class/public%20Election--getLastTimer.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Timber benchmarking](https://github.com/julien-boudry/Condorcet/wiki/III-%23-A.-Avanced-features---Configuration-%23-1.-Timer-Benchmarking)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getImplicitRankingRule.md b/include/Condorcet/Documentation/Election Class/public Election--getImplicitRankingRule.md new file mode 100644 index 0000000..9f60e7e --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getImplicitRankingRule.md @@ -0,0 +1,23 @@ +## public Election::getImplicitRankingRule + +### Description + +```php +public Election->getImplicitRankingRule ( ): bool +``` + +Returns the corresponding setting as currently set (True by default). +If it is True then all votes expressing a partial ranking are understood as implicitly placing all the non-mentioned candidates exequos on a last rank. +If it is false, then the candidates not ranked, are not taken into account at all. + + +### Return value: + +*(```bool```)* True / False + + +--------------------------------------- + +### Related method(s) + +* [Election::setImplicitRanking](../Election%20Class/public%20Election--setImplicitRanking.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getLastTimer.md b/include/Condorcet/Documentation/Election Class/public Election--getLastTimer.md new file mode 100644 index 0000000..fd7d529 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getLastTimer.md @@ -0,0 +1,27 @@ +## public Election::getLastTimer + +### Description + +```php +public Election->getLastTimer ( ): float +``` + +Return the last computation runtime (typically after a getResult() call.). Include only computation related methods. + + +### Return value: + +*(```float```)* (Float) Timer + + +--------------------------------------- + +### Related method(s) + +* [Election::getGlobalTimer](../Election%20Class/public%20Election--getGlobalTimer.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Timber benchmarking](https://github.com/julien-boudry/Condorcet/wiki/III-%23-A.-Avanced-features---Configuration-%23-1.-Timer-Benchmarking)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getLoser.md b/include/Condorcet/Documentation/Election Class/public Election--getLoser.md new file mode 100644 index 0000000..221bc95 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getLoser.md @@ -0,0 +1,40 @@ +## public Election::getLoser + +### Description + +```php +public Election->getLoser ( [?string $method = null] ): CondorcetPHP\Condorcet\Candidate|array|null +``` + +Get the natural Condorcet loser if there is one. Alternatively you can get the loser(s) from an advanced Condorcet algorithm. + + +##### **method:** *```?string```* +*Only if not nulle:* + +The loser will be provided by an advanced algorithm of an available advanced Condorcet method. For most of them, it will be the same as the Condorcet Marquis there. But if it does not exist, it may be different; and in some cases they may be multiple. + + If null, Natural Condorcet algorithm will be use. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Candidate|array|null```)* Candidate object given. Null if there are no available winner or loser. + +If you use an advanced method instead of Natural, you can get an array with multiples losers. + +Throw an exception on error. + + +--------------------------------------- + +### Related method(s) + +* [Election::getWinner](../Election%20Class/public%20Election--getWinner.md) +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Natural Condorcet](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-1.-Natural-Condorcet)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getNumberOfSeats.md b/include/Condorcet/Documentation/Election Class/public Election--getNumberOfSeats.md new file mode 100644 index 0000000..03621dd --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getNumberOfSeats.md @@ -0,0 +1,22 @@ +## public Election::getNumberOfSeats + +### Description + +```php +public Election->getNumberOfSeats ( ): int +``` + +Get number of Seats for STV methods. + + +### Return value: + +*(```int```)* Number of seats. + + +--------------------------------------- + +### Related method(s) + +* [Election::setNumberOfSeats](../Election%20Class/public%20Election--setNumberOfSeats.md) +* [Result::getNumberOfSeats](../Result%20Class/public%20Result--getNumberOfSeats.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getObjectVersion.md b/include/Condorcet/Documentation/Election Class/public Election--getObjectVersion.md new file mode 100644 index 0000000..5670e49 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getObjectVersion.md @@ -0,0 +1,25 @@ +## public Election::getObjectVersion + +### Description + +```php +public Election->getObjectVersion ( [bool $major = false] ): string +``` + +Get the Condorcet PHP version who built this Election object. Usefull pour serializing Election. + + +##### **major:** *```bool```* +true will return : '2.0' and false will return : '2.0.0'. + + +### Return value: + +*(```string```)* Condorcet PHP version. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getVersion](../Condorcet%20Class/public%20static%20Condorcet--getVersion.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getPairwise.md b/include/Condorcet/Documentation/Election Class/public Election--getPairwise.md new file mode 100644 index 0000000..6dd0e86 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getPairwise.md @@ -0,0 +1,28 @@ +## public Election::getPairwise + +### Description + +```php +public Election->getPairwise ( ): CondorcetPHP\Condorcet\Algo\Pairwise +``` + +Return the Pairwise. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Algo\Pairwise```)* Pairwise object. + + +--------------------------------------- + +### Related method(s) + +* [Election::getExplicitPairwise](../Election%20Class/public%20Election--getExplicitPairwise.md) +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Advanced Results](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-4.-Advanced-Results-Management)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getResult.md b/include/Condorcet/Documentation/Election Class/public Election--getResult.md new file mode 100644 index 0000000..044cd8a --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getResult.md @@ -0,0 +1,42 @@ +## public Election::getResult + +### Description + +```php +public Election->getResult ( [?string $method = null , array $methodOptions = []] ): CondorcetPHP\Condorcet\Result +``` + +Get a full ranking from an advanced Condorcet method. +*Have a look on the [supported method](https://github.com/julien-boudry/Condorcet/wiki/I-%23-Installation-%26-Basic-Configuration-%23-2.-Condorcet-Methods), or create [your own algorithm](https://github.com/julien-boudry/Condorcet/wiki/III-%23-C.-Extending-Condorcet-%23-1.-Add-your-own-ranking-algorithm).* + + +##### **method:** *```?string```* +Not required for use election default method. Set the string name of the algorithm for use of a specific one. + + +##### **methodOptions:** *```array```* +Array of option for some methods. Look at each method documentation. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Result```)* An Condorcet/Result Object (implementing ArrayAccess and Iterator, can be use like an array ordered by rank) + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\AlgorithmException``` + +--------------------------------------- + +### Related method(s) + +* [Election::getWinner](../Election%20Class/public%20Election--getWinner.md) +* [Condorcet::getDefaultMethod](../Condorcet%20Class/public%20Condorcet--getDefaultMethod.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Ranking from Condorcet Method](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-2.-Get-Ranking-from-Condorcet-advanced-Methods)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--getState.md b/include/Condorcet/Documentation/Election Class/public Election--getState.md new file mode 100644 index 0000000..824aeb0 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getState.md @@ -0,0 +1,23 @@ +## public Election::getState + +### Description + +```php +public Election->getState ( ): CondorcetPHP\Condorcet\ElectionProcess\ElectionState +``` + +Get the election process level. + + +### Return value: + +*(```CondorcetPHP\Condorcet\ElectionProcess\ElectionState```)* ElectionState::CANDIDATES_REGISTRATION: Candidate registered state. No votes, no result, no cache. +ElectionState::VOTES_REGISTRATION: Voting registration phase. Pairwise cache can exist thanks to dynamic computation if voting phase continue after the first get result. But method result never exist. +3: Result phase: Some method result may exist, pairwise exist. An election will return to Phase 2 if votes are added or modified dynamically. + + +--------------------------------------- + +### Related method(s) + +* [Election::setStateToVote](../Election%20Class/public%20Election--setStateToVote.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getStatsVerbosity.md b/include/Condorcet/Documentation/Election Class/public Election--getStatsVerbosity.md new file mode 100644 index 0000000..b133c3e --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getStatsVerbosity.md @@ -0,0 +1,15 @@ +## public Election::getStatsVerbosity + +### Description + +```php +public Election->getStatsVerbosity ( ): CondorcetPHP\Condorcet\Algo\StatsVerbosity +``` + +The current level of stats verbosity for this election object. Look at Election->setStatsVerbosity method for more informations. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Algo\StatsVerbosity```)* The current verbosity level for this election object. + diff --git a/include/Condorcet/Documentation/Election Class/public Election--getTimerManager.md b/include/Condorcet/Documentation/Election Class/public Election--getTimerManager.md new file mode 100644 index 0000000..6683f31 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getTimerManager.md @@ -0,0 +1,22 @@ +## public Election::getTimerManager + +### Description + +```php +public Election->getTimerManager ( ): CondorcetPHP\Condorcet\Timer\Manager +``` + +Get the Timer manager object. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Timer\Manager```)* An CondorcetPHP\Condorcet\Timer\Manager object using by this election. + + +--------------------------------------- + +### Related method(s) + +* [Election::getGlobalTimer](../Election%20Class/public%20Election--getGlobalTimer.md) +* [Election::getLastTimer](../Election%20Class/public%20Election--getLastTimer.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getVotesList.md b/include/Condorcet/Documentation/Election Class/public Election--getVotesList.md new file mode 100644 index 0000000..37d269c --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getVotesList.md @@ -0,0 +1,30 @@ +## public Election::getVotesList + +### Description + +```php +public Election->getVotesList ( [array|string|null $tags = null , bool $with = true] ): array +``` + +Get registered vote list. + + +##### **tags:** *```array|string|null```* +Tags list as a string separated by commas or array. + + +##### **with:** *```bool```* +Get votes with these tags or without. + + +### Return value: + +*(```array```)* Populated by each Vote object. + + +--------------------------------------- + +### Related method(s) + +* [Election::countVotes](../Election%20Class/public%20Election--countVotes.md) +* [Election::getVotesListAsString](../Election%20Class/public%20Election--getVotesListAsString.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getVotesListAsString.md b/include/Condorcet/Documentation/Election Class/public Election--getVotesListAsString.md new file mode 100644 index 0000000..002c5f4 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getVotesListAsString.md @@ -0,0 +1,27 @@ +## public Election::getVotesListAsString + +### Description + +```php +public Election->getVotesListAsString ( [bool $withContext = true] ): string +``` + +Get registered vote list. + + +##### **withContext:** *```bool```* +Depending of the implicit ranking rule of the election, will complete or not the ranking. If $withContext is false, ranking are never adapted to the context. + + +### Return value: + +*(```string```)* Return a string like :
+A > B > C * 3
+A = B > C * 6 + + +--------------------------------------- + +### Related method(s) + +* [Election::parseVotes](../Election%20Class/public%20Election--parseVotes.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getVotesListGenerator.md b/include/Condorcet/Documentation/Election Class/public Election--getVotesListGenerator.md new file mode 100644 index 0000000..e439019 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getVotesListGenerator.md @@ -0,0 +1,30 @@ +## public Election::getVotesListGenerator + +### Description + +```php +public Election->getVotesListGenerator ( [array|string|null $tags = null , bool $with = true] ): Generator +``` + +Same as Election::getVotesList. But Return a PHP generator object. +Usefull if your work on very large election with an external DataHandler, because it's will not using large memory amount. + + +##### **tags:** *```array|string|null```* +Tags list as a string separated by commas or array. + + +##### **with:** *```bool```* +Get votes with these tags or without. + + +### Return value: + +*(```Generator```)* Populated by each Vote object. + + +--------------------------------------- + +### Related method(s) + +* [Election::getVotesList](../Election%20Class/public%20Election--getVotesList.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getVotesValidUnderConstraintGenerator.md b/include/Condorcet/Documentation/Election Class/public Election--getVotesValidUnderConstraintGenerator.md new file mode 100644 index 0000000..98bb164 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getVotesValidUnderConstraintGenerator.md @@ -0,0 +1,31 @@ +## public Election::getVotesValidUnderConstraintGenerator + +### Description + +```php +public Election->getVotesValidUnderConstraintGenerator ( [array|string|null $tags = null , bool $with = true] ): Generator +``` + +Same as Election::getVotesList. But Return a PHP generator object. +Usefull if your work on very large election with an external DataHandler, because it's will not using large memory amount. + + +##### **tags:** *```array|string|null```* +Tags list as a string separated by commas or array. + + +##### **with:** *```bool```* +Get votes with these tags or without. + + +### Return value: + +*(```Generator```)* Populated by each Vote object. + + +--------------------------------------- + +### Related method(s) + +* [Election::getVotesListGenerator](../Election%20Class/public%20Election--getVotesListGenerator.md) +* [Election::getVotesList](../Election%20Class/public%20Election--getVotesList.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--getWinner.md b/include/Condorcet/Documentation/Election Class/public Election--getWinner.md new file mode 100644 index 0000000..e4358e8 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--getWinner.md @@ -0,0 +1,41 @@ +## public Election::getWinner + +### Description + +```php +public Election->getWinner ( [?string $method = null] ): CondorcetPHP\Condorcet\Candidate|array|null +``` + +Get the natural Condorcet winner if there is one. Alternatively you can get the winner(s) from an advanced Condorcet algorithm. + + +##### **method:** *```?string```* +*Only if not null: * + +The winner will be provided by an advanced algorithm of an available advanced Condorcet method. For most of them, it will be the same as the Condorcet Marquis there. But if it does not exist, it may be different; and in some cases they may be multiple. + +If null, Natural Condorcet algorithm will be use. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Candidate|array|null```)* Candidate object given. Null if there are no available winner or loser. + +If you use an advanced method instead of Natural, you can get an array with multiples winners. + +Throw an exception on error. + + +--------------------------------------- + +### Related method(s) + +* [Election::getCondorcetWinner](../Election%20Class/public%20Election--getCondorcetWinner.md) +* [Election::getLoser](../Election%20Class/public%20Election--getLoser.md) +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Natural Condorcet](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-1.-Natural-Condorcet)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--isRegisteredCandidate.md b/include/Condorcet/Documentation/Election Class/public Election--isRegisteredCandidate.md new file mode 100644 index 0000000..345d7db --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--isRegisteredCandidate.md @@ -0,0 +1,30 @@ +## public Election::isRegisteredCandidate + +### Description + +```php +public Election->isRegisteredCandidate ( CondorcetPHP\Condorcet\Candidate|string $candidate [, bool $strictMode = true] ): bool +``` + +Check if a candidate is already taking part in the election. + + +##### **candidate:** *```CondorcetPHP\Condorcet\Candidate|string```* +Candidate object or candidate string name. String name works only if the strict mode is active. + + +##### **strictMode:** *```bool```* +Search comparison mode. In strict mode, candidate objects are compared strictly and a string input can't match anything. +If strict mode is false, the comparison will be based on name. + + +### Return value: + +*(```bool```)* True / False + + +--------------------------------------- + +### Related method(s) + +* [Election::addCandidate](../Election%20Class/public%20Election--addCandidate.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--isVoteWeightAllowed.md b/include/Condorcet/Documentation/Election Class/public Election--isVoteWeightAllowed.md new file mode 100644 index 0000000..e43be9a --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--isVoteWeightAllowed.md @@ -0,0 +1,22 @@ +## public Election::isVoteWeightAllowed + +### Description + +```php +public Election->isVoteWeightAllowed ( ): bool +``` + +Returns the corresponding setting as currently set (False by default). +If it is True then votes vote optionally can use weight otherwise (if false) all votes will be evaluated as equal for this election. + + +### Return value: + +*(```bool```)* True / False + + +--------------------------------------- + +### Related method(s) + +* [Election::allowsVoteWeight](../Election%20Class/public%20Election--allowsVoteWeight.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--parseCandidates.md b/include/Condorcet/Documentation/Election Class/public Election--parseCandidates.md new file mode 100644 index 0000000..90c64d3 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--parseCandidates.md @@ -0,0 +1,43 @@ +## public Election::parseCandidates + +### Description + +```php +public Election->parseCandidates ( string $input [, bool $isFile = false] ): array +``` + +Import candidate from a text source. + + +##### **input:** *```string```* +String or valid path to a text file. + + +##### **isFile:** *```bool```* +If true, the input is evaluated as path to a text file. + + +### Return value: + +*(```array```)* List of newly registered candidate object. Count it for checking if all candidates have been correctly registered. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\CandidateExistsException``` +* ```CondorcetPHP\Condorcet\Throwable\VoteMaxNumberReachedException``` + +--------------------------------------- + +### Related method(s) + +* [Election::addCandidate](../Election%20Class/public%20Election--addCandidate.md) +* [Election::addCandidatesFromJson](../Election%20Class/public%20Election--addCandidatesFromJson.md) +* [Election::parseVotes](../Election%20Class/public%20Election--parseVotes.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Manage Candidates](https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--parseVotes.md b/include/Condorcet/Documentation/Election Class/public Election--parseVotes.md new file mode 100644 index 0000000..8d36e24 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--parseVotes.md @@ -0,0 +1,38 @@ +## public Election::parseVotes + +### Description + +```php +public Election->parseVotes ( string $input [, bool $isFile = false] ): int +``` + +Import votes from a text source. If any invalid vote is found inside, nothing are registered. + + +##### **input:** *```string```* +String or valid path to a text file. + + +##### **isFile:** *```bool```* +If true, the input is evalatued as path to text file. + + +### Return value: + +*(```int```)* Count of the new registered vote. + + +--------------------------------------- + +### Related method(s) + +* [Election::addVote](../Election%20Class/public%20Election--addVote.md) +* [Election::parseCandidates](../Election%20Class/public%20Election--parseCandidates.md) +* [Election::parseVotesWithoutFail](../Election%20Class/public%20Election--parseVotesWithoutFail.md) +* [Election::addVotesFromJson](../Election%20Class/public%20Election--addVotesFromJson.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Add Vote](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--parseVotesWithoutFail.md b/include/Condorcet/Documentation/Election Class/public Election--parseVotesWithoutFail.md new file mode 100644 index 0000000..c504917 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--parseVotesWithoutFail.md @@ -0,0 +1,42 @@ +## public Election::parseVotesWithoutFail + +### Description + +```php +public Election->parseVotesWithoutFail ( SplFileInfo|string $input [, bool $isFile = false , ?Closure $callBack = null] ): int +``` + +Similar to parseVote method. But will ignore invalid line. This method is also far less greedy in memory and must be prefered for very large file input. And to combine with the use of an external data handler. + + +##### **input:** *```SplFileInfo|string```* +String, valid path to a text file or an object SplFileInfo or extending it like SplFileObject. + + +##### **isFile:** *```bool```* +If true, the string input is evalatued as path to text file. + + +##### **callBack:** *```?Closure```* +Callback function to execute after each registered vote. + + +### Return value: + +*(```int```)* Number of invalid records into input (except empty lines). It's not an invalid votes count. Check Election::countVotes if you want to be sure. + + +--------------------------------------- + +### Related method(s) + +* [Election::addVote](../Election%20Class/public%20Election--addVote.md) +* [Election::parseCandidates](../Election%20Class/public%20Election--parseCandidates.md) +* [Election::parseVotes](../Election%20Class/public%20Election--parseVotes.md) +* [Election::addVotesFromJson](../Election%20Class/public%20Election--addVotesFromJson.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Add Vote](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--removeCandidates.md b/include/Condorcet/Documentation/Election Class/public Election--removeCandidates.md new file mode 100644 index 0000000..2bff883 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--removeCandidates.md @@ -0,0 +1,43 @@ +## public Election::removeCandidates + +### Description + +```php +public Election->removeCandidates ( CondorcetPHP\Condorcet\Candidate|array|string $candidates_input ): array +``` + +Remove candidates from an election. + +*Please note: You can't remove candidates after the first vote. An exception will be thrown.* + + +##### **candidates_input:** *```CondorcetPHP\Condorcet\Candidate|array|string```* +* String matching candidate name +* CondorcetPHP\Condorcet\Candidate object +* Array populated by CondorcetPHP\Condorcet\Candidate +* Array populated by string matching candidate name. + + +### Return value: + +*(```array```)* List of removed CondorcetPHP\Condorcet\Candidate object. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\CandidateDoesNotExistException``` +* ```CondorcetPHP\Condorcet\Throwable\VotingHasStartedException``` + +--------------------------------------- + +### Related method(s) + +* [Election::addCandidate](../Election%20Class/public%20Election--addCandidate.md) +* [Election::getCandidatesList](../Election%20Class/public%20Election--getCandidatesList.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Manage Candidate](https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--removeExternalDataHandler.md b/include/Condorcet/Documentation/Election Class/public Election--removeExternalDataHandler.md new file mode 100644 index 0000000..74d1f9f --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--removeExternalDataHandler.md @@ -0,0 +1,26 @@ +## public Election::removeExternalDataHandler + +### Description + +```php +public Election->removeExternalDataHandler ( ): bool +``` + +Remove an external driver to store vote on very large election. And import his data into classical memory. + + +### Return value: + +*(```bool```)* True if success. Else throw an Exception. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\DataHandlerException``` + +--------------------------------------- + +### Related method(s) + +* [Election::setExternalDataHandler](../Election%20Class/public%20Election--setExternalDataHandler.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--removeVote.md b/include/Condorcet/Documentation/Election Class/public Election--removeVote.md new file mode 100644 index 0000000..835b96c --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--removeVote.md @@ -0,0 +1,33 @@ +## public Election::removeVote + +### Description + +```php +public Election->removeVote ( CondorcetPHP\Condorcet\Vote $vote ): bool +``` + +Remove Votes from an election. + + +##### **vote:** *```CondorcetPHP\Condorcet\Vote```* +Vote object. + + +### Return value: + +*(```bool```)* List of removed CondorcetPHP\Condorcet\Vote object. + + +--------------------------------------- + +### Related method(s) + +* [Election::addVote](../Election%20Class/public%20Election--addVote.md) +* [Election::getVotesList](../Election%20Class/public%20Election--getVotesList.md) +* [Election::removeVotesByTags](../Election%20Class/public%20Election--removeVotesByTags.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Vote management](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-2.-Manage-Vote)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--removeVotesByTags.md b/include/Condorcet/Documentation/Election Class/public Election--removeVotesByTags.md new file mode 100644 index 0000000..d19a183 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--removeVotesByTags.md @@ -0,0 +1,44 @@ +## public Election::removeVotesByTags + +### Description + +```php +public Election->removeVotesByTags ( array|string $tags [, bool $with = true] ): array +``` + +Remove Vote from an election using tags. + +```php +$election->removeVotesByTags('Charlie') ; // Remove vote(s) with tag Charlie +$election->removeVotesByTags('Charlie', false) ; // Remove votes without tag Charlie +$election->removeVotesByTags('Charlie, Julien', false) ; // Remove votes without tag Charlie AND without tag Julien. +$election->removeVotesByTags(array('Julien','Charlie')) ; // Remove votes with tag Charlie OR with tag Julien. +``` + + +##### **tags:** *```array|string```* +Tags as string separated by commas or array. + + +##### **with:** *```bool```* +Votes with these tags or without. + + +### Return value: + +*(```array```)* List of removed CondorcetPHP\Condorcet\Vote object. + + +--------------------------------------- + +### Related method(s) + +* [Election::addVote](../Election%20Class/public%20Election--addVote.md) +* [Election::getVotesList](../Election%20Class/public%20Election--getVotesList.md) +* [Election::removeVotes](../Election%20Class/public%20Election--removeVotes.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Vote management](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-2.-Manage-Vote)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--setExternalDataHandler.md b/include/Condorcet/Documentation/Election Class/public Election--setExternalDataHandler.md new file mode 100644 index 0000000..3ba98be --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--setExternalDataHandler.md @@ -0,0 +1,36 @@ +## public Election::setExternalDataHandler + +### Description + +```php +public Election->setExternalDataHandler ( CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface $driver ): bool +``` + +Import and enable an external driver to store vote on very large election. + + +##### **driver:** *```CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface```* +Driver object. + + +### Return value: + +*(```bool```)* True if success. Else throw an Exception. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\DataHandlerException``` + +--------------------------------------- + +### Related method(s) + +* [Election::removeExternalDataHandler](../Election%20Class/public%20Election--removeExternalDataHandler.md) + +--------------------------------------- + +### Examples and explanation + +* **[[Manual - DataHandler]](https://github.com/julien-boudry/Condorcet/blob/master/examples/specifics_examples/use_large_election_external_database_drivers.php)** diff --git a/include/Condorcet/Documentation/Election Class/public Election--setImplicitRanking.md b/include/Condorcet/Documentation/Election Class/public Election--setImplicitRanking.md new file mode 100644 index 0000000..75ba5be --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--setImplicitRanking.md @@ -0,0 +1,27 @@ +## public Election::setImplicitRanking + +### Description + +```php +public Election->setImplicitRanking ( [bool $rule = true] ): bool +``` + +Set the setting and reset all result data. +If it is True then all votes expressing a partial ranking are understood as implicitly placing all the non-mentioned candidates exequos on a last rank. +If it is false, then the candidates not ranked, are not taken into account at all. + + +##### **rule:** *```bool```* +New rule. + + +### Return value: + +*(```bool```)* Return True + + +--------------------------------------- + +### Related method(s) + +* [Election::getImplicitRankingRule](../Election%20Class/public%20Election--getImplicitRankingRule.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--setMethodOption.md b/include/Condorcet/Documentation/Election Class/public Election--setMethodOption.md new file mode 100644 index 0000000..7fd77d6 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--setMethodOption.md @@ -0,0 +1,33 @@ +## public Election::setMethodOption + +### Description + +```php +public Election->setMethodOption ( string $method , string $optionName , BackedEnum|array|string|int|float $optionValue ): bool +``` + +Set an option to a method module and reset his cache for this election object. Be aware that this option applies to all election objects and remains in memory. + + +##### **method:** *```string```* +Method name or class path. + + +##### **optionName:** *```string```* +Option name. + + +##### **optionValue:** *```BackedEnum|array|string|int|float```* +Option Value. + + +### Return value: + +*(```bool```)* True on success. Else False. + + +--------------------------------------- + +### Related method(s) + +* [Result::getMethodOptions](../Result%20Class/public%20Result--getMethodOptions.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--setNumberOfSeats.md b/include/Condorcet/Documentation/Election Class/public Election--setNumberOfSeats.md new file mode 100644 index 0000000..8b94280 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--setNumberOfSeats.md @@ -0,0 +1,30 @@ +## public Election::setNumberOfSeats + +### Description + +```php +public Election->setNumberOfSeats ( int $seats ): int +``` + +Set number of Seats for STV methods. + + +##### **seats:** *```int```* +The number of seats for proportional methods. + + +### Return value: + +*(```int```)* Number of seats. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\NoSeatsException``` + +--------------------------------------- + +### Related method(s) + +* [Election::getNumberOfSeats](../Election%20Class/public%20Election--getNumberOfSeats.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--setStateToVote.md b/include/Condorcet/Documentation/Election Class/public Election--setStateToVote.md new file mode 100644 index 0000000..abe1a2d --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--setStateToVote.md @@ -0,0 +1,31 @@ +## public Election::setStateToVote + +### Description + +```php +public Election->setStateToVote ( ): bool +``` + +Force the election to get back to state 2. See Election::getState. +It is not necessary to use this method. The election knows how to manage its phase changes on its own. But it is a way to clear the cache containing the results of the methods. + +If you are on state 1 (candidate registering), it's will close this state and prepare election to get firsts votes. +If you are on state 3. The method result cache will be clear, but not the pairwise. Which will continue to be updated dynamically. + + +### Return value: + +*(```bool```)* Always True. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\NoCandidatesException``` +* ```CondorcetPHP\Condorcet\Throwable\ResultRequestedWithoutVotesException``` + +--------------------------------------- + +### Related method(s) + +* [Election::getState](../Election%20Class/public%20Election--getState.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--setStatsVerbosity.md b/include/Condorcet/Documentation/Election Class/public Election--setStatsVerbosity.md new file mode 100644 index 0000000..c9fe09a --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--setStatsVerbosity.md @@ -0,0 +1,20 @@ +## public Election::setStatsVerbosity + +### Description + +```php +public Election->setStatsVerbosity ( CondorcetPHP\Condorcet\Algo\StatsVerbosity $StatsVerbosity ): void +``` + +Set a verbosity level for Result->statsVerbosity on returning Result objects. High level can slow down processing and use more memory (many more) than LOW and STD (default) level on somes methods. + + +##### **StatsVerbosity:** *```CondorcetPHP\Condorcet\Algo\StatsVerbosity```* +A verbosity level. + +--------------------------------------- + +### Related method(s) + +* [Election::getVerbosity](../Election%20Class/public%20Election--getVerbosity.md) +* [Result::getVerbosity](../Result%20Class/public%20Result--getVerbosity.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--sumValidVotesWeightWithConstraints.md b/include/Condorcet/Documentation/Election Class/public Election--sumValidVotesWeightWithConstraints.md new file mode 100644 index 0000000..d45bc50 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--sumValidVotesWeightWithConstraints.md @@ -0,0 +1,22 @@ +## public Election::sumValidVotesWeightWithConstraints + +### Description + +```php +public Election->sumValidVotesWeightWithConstraints ( ): int +``` + +Sum total votes weight in this election. If vote weight functionality is disable (default setting), it will return the number of registered votes. This method don't ignore votes constraints, only valid vote will be counted. + + +### Return value: + +*(```int```)* (Int) Total vote weight + + +--------------------------------------- + +### Related method(s) + +* [Election::countValidVoteWithConstraints](../Election%20Class/public%20Election--countValidVoteWithConstraints.md) +* [Election::countInvalidVoteWithConstraints](../Election%20Class/public%20Election--countInvalidVoteWithConstraints.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--sumVotesWeight.md b/include/Condorcet/Documentation/Election Class/public Election--sumVotesWeight.md new file mode 100644 index 0000000..bff22d3 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--sumVotesWeight.md @@ -0,0 +1,21 @@ +## public Election::sumVotesWeight + +### Description + +```php +public Election->sumVotesWeight ( ): int +``` + +Sum total votes weight in this election. If vote weight functionality is disable (default setting), it will return the number of registered votes. This method ignore votes constraints. + + +### Return value: + +*(```int```)* (Int) Total vote weight + + +--------------------------------------- + +### Related method(s) + +* [Election::sumValidVotesWeightWithConstraints](../Election%20Class/public%20Election--sumValidVotesWeightWithConstraints.md) diff --git a/include/Condorcet/Documentation/Election Class/public Election--testIfVoteIsValidUnderElectionConstraints.md b/include/Condorcet/Documentation/Election Class/public Election--testIfVoteIsValidUnderElectionConstraints.md new file mode 100644 index 0000000..750e41e --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public Election--testIfVoteIsValidUnderElectionConstraints.md @@ -0,0 +1,33 @@ +## public Election::testIfVoteIsValidUnderElectionConstraints + +### Description + +```php +public Election->testIfVoteIsValidUnderElectionConstraints ( CondorcetPHP\Condorcet\Vote $vote ): bool +``` + +Test if a vote is valid with these election constraints. + + +##### **vote:** *```CondorcetPHP\Condorcet\Vote```* +A vote. Not necessarily registered in this election. + + +### Return value: + +*(```bool```)* Return True if vote will pass the constraints rules, else False. + + +--------------------------------------- + +### Related method(s) + +* [Election::getConstraints](../Election%20Class/public%20Election--getConstraints.md) +* [Election::addConstraints](../Election%20Class/public%20Election--addConstraints.md) +* [Election::clearConstraints](../Election%20Class/public%20Election--clearConstraints.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Vote Constraints](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-5.-Vote-Constraints)** diff --git a/include/Condorcet/Documentation/Election Class/public static Election--setMaxParseIteration.md b/include/Condorcet/Documentation/Election Class/public static Election--setMaxParseIteration.md new file mode 100644 index 0000000..d9fd5b9 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public static Election--setMaxParseIteration.md @@ -0,0 +1,25 @@ +## public static Election::setMaxParseIteration + +### Description + +```php +public static Election::setMaxParseIteration ( ?int $maxParseIterations ): ?int +``` + +Maximum input for each use of Election::parseCandidate && Election::parseVote. Will throw an exception if exceeded. + + +##### **maxParseIterations:** *```?int```* +Null will deactivate this functionality. Else, enter an integer. + + +### Return value: + +*(```?int```)* *(int or null)* The new limit. + + +--------------------------------------- + +### Related method(s) + +* [static Election::setMaxVoteNumber](../Election%20Class/public%20static%20Election--setMaxVoteNumber.md) diff --git a/include/Condorcet/Documentation/Election Class/public static Election--setMaxVoteNumber.md b/include/Condorcet/Documentation/Election Class/public static Election--setMaxVoteNumber.md new file mode 100644 index 0000000..0d12841 --- /dev/null +++ b/include/Condorcet/Documentation/Election Class/public static Election--setMaxVoteNumber.md @@ -0,0 +1,25 @@ +## public static Election::setMaxVoteNumber + +### Description + +```php +public static Election::setMaxVoteNumber ( ?int $maxVotesNumber ): ?int +``` + +Add a limitation on Election::addVote and related methods. You can't add new vote y the number of registered vote is equall ou superior of this limit. + + +##### **maxVotesNumber:** *```?int```* +Null will deactivate this functionality. An integer will fix the limit. + + +### Return value: + +*(```?int```)* *(int or null)* The new limit. + + +--------------------------------------- + +### Related method(s) + +* [static Election::setMaxParseIteration](../Election%20Class/public%20static%20Election--setMaxParseIteration.md) diff --git a/include/Condorcet/Documentation/README.md b/include/Condorcet/Documentation/README.md new file mode 100644 index 0000000..8e66e7c --- /dev/null +++ b/include/Condorcet/Documentation/README.md @@ -0,0 +1,2295 @@ +> **[Presentation](../README.md) | [Manual](https://github.com/julien-boudry/Condorcet/wiki) | Methods References | [Tests](../Tests)** + +# Public API Index*_ +_*: I try to update and complete the documentation. See also [the manual](https://github.com/julien-boudry/Condorcet/wiki), [the tests](../Tests) also produce many examples. And create issues for questions or fixing documentation!_ + + +### CondorcetPHP\Condorcet\Candidate Class + +* [public Candidate->__construct (...)](Candidate%20Class/public%20Candidate--__construct.md) +* [public Candidate->countLinks ()](Candidate%20Class/public%20Candidate--countLinks.md): ```int``` +* [public Candidate->getCreateTimestamp ()](Candidate%20Class/public%20Candidate--getCreateTimestamp.md): ```float``` +* [public Candidate->getHistory ()](Candidate%20Class/public%20Candidate--getHistory.md): ```array``` +* [public Candidate->getLinks ()](Candidate%20Class/public%20Candidate--getLinks.md): ```WeakMap``` +* [public Candidate->getName ()](Candidate%20Class/public%20Candidate--getName.md): ```string``` +* [public Candidate->getObjectVersion (...)](Candidate%20Class/public%20Candidate--getObjectVersion.md): ```string``` +* [public Candidate->getProvisionalState ()](Candidate%20Class/public%20Candidate--getProvisionalState.md): ```bool``` +* [public Candidate->getTimestamp ()](Candidate%20Class/public%20Candidate--getTimestamp.md): ```float``` +* [public Candidate->haveLink (...)](Candidate%20Class/public%20Candidate--haveLink.md): ```bool``` +* [public Candidate->setName (...)](Candidate%20Class/public%20Candidate--setName.md): ```bool``` + +### CondorcetPHP\Condorcet\Condorcet Class + +* ```final public const VERSION: (string)``` +* ```final public const CONDORCET_BASIC_CLASS: (string)``` + +* [public static Condorcet::addMethod (...)](Condorcet%20Class/public%20static%20Condorcet--addMethod.md): ```bool``` +* [public static Condorcet::getAuthMethods (...)](Condorcet%20Class/public%20static%20Condorcet--getAuthMethods.md): ```array``` +* [public static Condorcet::getDefaultMethod ()](Condorcet%20Class/public%20static%20Condorcet--getDefaultMethod.md): ```?string``` +* [public static Condorcet::getMethodClass (...)](Condorcet%20Class/public%20static%20Condorcet--getMethodClass.md): ```?string``` +* [public static Condorcet::getVersion (...)](Condorcet%20Class/public%20static%20Condorcet--getVersion.md): ```string``` +* [public static Condorcet::isAuthMethod (...)](Condorcet%20Class/public%20static%20Condorcet--isAuthMethod.md): ```bool``` +* [public static Condorcet::setDefaultMethod (...)](Condorcet%20Class/public%20static%20Condorcet--setDefaultMethod.md): ```bool``` + +### CondorcetPHP\Condorcet\Election Class + +* ```public const MAX_CANDIDATE_NAME_LENGTH: (integer)``` + +* [public static Election::setMaxParseIteration (...)](Election%20Class/public%20static%20Election--setMaxParseIteration.md): ```?int``` +* [public static Election::setMaxVoteNumber (...)](Election%20Class/public%20static%20Election--setMaxVoteNumber.md): ```?int``` +* [public Election->__construct ()](Election%20Class/public%20Election--__construct.md) +* [public Election->addCandidate (...)](Election%20Class/public%20Election--addCandidate.md): ```CondorcetPHP\Condorcet\Candidate``` +* [public Election->addCandidatesFromJson (...)](Election%20Class/public%20Election--addCandidatesFromJson.md): ```array``` +* [public Election->addConstraint (...)](Election%20Class/public%20Election--addConstraint.md): ```bool``` +* [public Election->addVote (...)](Election%20Class/public%20Election--addVote.md): ```CondorcetPHP\Condorcet\Vote``` +* [public Election->addVotesFromJson (...)](Election%20Class/public%20Election--addVotesFromJson.md): ```int``` +* [public Election->allowsVoteWeight (...)](Election%20Class/public%20Election--allowsVoteWeight.md): ```bool``` +* [public Election->canAddCandidate (...)](Election%20Class/public%20Election--canAddCandidate.md): ```bool``` +* [public Election->clearConstraints ()](Election%20Class/public%20Election--clearConstraints.md): ```bool``` +* [public Election->computeResult (...)](Election%20Class/public%20Election--computeResult.md): ```void``` +* [public Election->countCandidates ()](Election%20Class/public%20Election--countCandidates.md): ```int``` +* [public Election->countInvalidVoteWithConstraints ()](Election%20Class/public%20Election--countInvalidVoteWithConstraints.md): ```int``` +* [public Election->countValidVoteWithConstraints ()](Election%20Class/public%20Election--countValidVoteWithConstraints.md): ```int``` +* [public Election->countVotes (...)](Election%20Class/public%20Election--countVotes.md): ```int``` +* [public Election->getCandidateObjectFromName (...)](Election%20Class/public%20Election--getCandidateObjectFromName.md): ```?CondorcetPHP\Condorcet\Candidate``` +* [public Election->getCandidatesList ()](Election%20Class/public%20Election--getCandidatesList.md): ```array``` +* [public Election->getCandidatesListAsString ()](Election%20Class/public%20Election--getCandidatesListAsString.md): ```array``` +* [public Election->getChecksum ()](Election%20Class/public%20Election--getChecksum.md): ```string``` +* [public Election->getCondorcetLoser ()](Election%20Class/public%20Election--getCondorcetLoser.md): ```?CondorcetPHP\Condorcet\Candidate``` +* [public Election->getCondorcetWinner ()](Election%20Class/public%20Election--getCondorcetWinner.md): ```?CondorcetPHP\Condorcet\Candidate``` +* [public Election->getConstraints ()](Election%20Class/public%20Election--getConstraints.md): ```array``` +* [public Election->getExplicitPairwise ()](Election%20Class/public%20Election--getExplicitPairwise.md): ```array``` +* [public Election->getGlobalTimer ()](Election%20Class/public%20Election--getGlobalTimer.md): ```float``` +* [public Election->getImplicitRankingRule ()](Election%20Class/public%20Election--getImplicitRankingRule.md): ```bool``` +* [public Election->getLastTimer ()](Election%20Class/public%20Election--getLastTimer.md): ```float``` +* [public Election->getLoser (...)](Election%20Class/public%20Election--getLoser.md): ```CondorcetPHP\Condorcet\Candidate|array|null``` +* [public Election->getNumberOfSeats ()](Election%20Class/public%20Election--getNumberOfSeats.md): ```int``` +* [public Election->getObjectVersion (...)](Election%20Class/public%20Election--getObjectVersion.md): ```string``` +* [public Election->getPairwise ()](Election%20Class/public%20Election--getPairwise.md): ```CondorcetPHP\Condorcet\Algo\Pairwise``` +* [public Election->getResult (...)](Election%20Class/public%20Election--getResult.md): ```CondorcetPHP\Condorcet\Result``` +* [public Election->getState ()](Election%20Class/public%20Election--getState.md): ```CondorcetPHP\Condorcet\ElectionProcess\ElectionState``` +* [public Election->getStatsVerbosity ()](Election%20Class/public%20Election--getStatsVerbosity.md): ```CondorcetPHP\Condorcet\Algo\StatsVerbosity``` +* [public Election->getTimerManager ()](Election%20Class/public%20Election--getTimerManager.md): ```CondorcetPHP\Condorcet\Timer\Manager``` +* [public Election->getVotesList (...)](Election%20Class/public%20Election--getVotesList.md): ```array``` +* [public Election->getVotesListAsString (...)](Election%20Class/public%20Election--getVotesListAsString.md): ```string``` +* [public Election->getVotesListGenerator (...)](Election%20Class/public%20Election--getVotesListGenerator.md): ```Generator``` +* [public Election->getVotesValidUnderConstraintGenerator (...)](Election%20Class/public%20Election--getVotesValidUnderConstraintGenerator.md): ```Generator``` +* [public Election->getWinner (...)](Election%20Class/public%20Election--getWinner.md): ```CondorcetPHP\Condorcet\Candidate|array|null``` +* [public Election->isRegisteredCandidate (...)](Election%20Class/public%20Election--isRegisteredCandidate.md): ```bool``` +* [public Election->isVoteWeightAllowed ()](Election%20Class/public%20Election--isVoteWeightAllowed.md): ```bool``` +* [public Election->parseCandidates (...)](Election%20Class/public%20Election--parseCandidates.md): ```array``` +* [public Election->parseVotes (...)](Election%20Class/public%20Election--parseVotes.md): ```int``` +* [public Election->parseVotesWithoutFail (...)](Election%20Class/public%20Election--parseVotesWithoutFail.md): ```int``` +* [public Election->removeCandidates (...)](Election%20Class/public%20Election--removeCandidates.md): ```array``` +* [public Election->removeExternalDataHandler ()](Election%20Class/public%20Election--removeExternalDataHandler.md): ```bool``` +* [public Election->removeVote (...)](Election%20Class/public%20Election--removeVote.md): ```bool``` +* [public Election->removeVotesByTags (...)](Election%20Class/public%20Election--removeVotesByTags.md): ```array``` +* [public Election->setExternalDataHandler (...)](Election%20Class/public%20Election--setExternalDataHandler.md): ```bool``` +* [public Election->setImplicitRanking (...)](Election%20Class/public%20Election--setImplicitRanking.md): ```bool``` +* [public Election->setMethodOption (...)](Election%20Class/public%20Election--setMethodOption.md): ```bool``` +* [public Election->setNumberOfSeats (...)](Election%20Class/public%20Election--setNumberOfSeats.md): ```int``` +* [public Election->setStateToVote ()](Election%20Class/public%20Election--setStateToVote.md): ```bool``` +* [public Election->setStatsVerbosity (...)](Election%20Class/public%20Election--setStatsVerbosity.md): ```void``` +* [public Election->sumValidVotesWeightWithConstraints ()](Election%20Class/public%20Election--sumValidVotesWeightWithConstraints.md): ```int``` +* [public Election->sumVotesWeight ()](Election%20Class/public%20Election--sumVotesWeight.md): ```int``` +* [public Election->testIfVoteIsValidUnderElectionConstraints (...)](Election%20Class/public%20Election--testIfVoteIsValidUnderElectionConstraints.md): ```bool``` + +### CondorcetPHP\Condorcet\Result Class + +* ```readonly public array $ranking``` +* ```readonly public array $rankingAsString``` +* ```readonly public ?int $seats``` +* ```readonly public array $methodOptions``` +* ```readonly public ?CondorcetPHP\Condorcet\Candidate $CondorcetWinner``` +* ```readonly public ?CondorcetPHP\Condorcet\Candidate $CondorcetLoser``` +* ```readonly public float $buildTimestamp``` +* ```readonly public string $fromMethod``` +* ```readonly public string $byClass``` +* ```readonly public CondorcetPHP\Condorcet\Algo\StatsVerbosity $statsVerbosity``` +* ```readonly public string $electionCondorcetVersion``` + +* [public Result->getBuildTimeStamp ()](Result%20Class/public%20Result--getBuildTimeStamp.md): ```float``` +* [public Result->getClassGenerator ()](Result%20Class/public%20Result--getClassGenerator.md): ```string``` +* [public Result->getCondorcetElectionGeneratorVersion ()](Result%20Class/public%20Result--getCondorcetElectionGeneratorVersion.md): ```string``` +* [public Result->getCondorcetLoser ()](Result%20Class/public%20Result--getCondorcetLoser.md): ```?CondorcetPHP\Condorcet\Candidate``` +* [public Result->getCondorcetWinner ()](Result%20Class/public%20Result--getCondorcetWinner.md): ```?CondorcetPHP\Condorcet\Candidate``` +* [public Result->getLoser ()](Result%20Class/public%20Result--getLoser.md): ```CondorcetPHP\Condorcet\Candidate|array|null``` +* [public Result->getMethod ()](Result%20Class/public%20Result--getMethod.md): ```string``` +* [public Result->getMethodOptions ()](Result%20Class/public%20Result--getMethodOptions.md): ```array``` +* [public Result->getNumberOfSeats ()](Result%20Class/public%20Result--getNumberOfSeats.md): ```?int``` +* [public Result->getObjectVersion (...)](Result%20Class/public%20Result--getObjectVersion.md): ```string``` +* [public Result->getOriginalResultArrayWithString ()](Result%20Class/public%20Result--getOriginalResultArrayWithString.md): ```array``` +* [public Result->getResultAsArray (...)](Result%20Class/public%20Result--getResultAsArray.md): ```array``` +* [public Result->getResultAsString ()](Result%20Class/public%20Result--getResultAsString.md): ```string``` +* [public Result->getStats ()](Result%20Class/public%20Result--getStats.md): ```mixed``` +* [public Result->getWarning (...)](Result%20Class/public%20Result--getWarning.md): ```array``` +* [public Result->getWinner ()](Result%20Class/public%20Result--getWinner.md): ```CondorcetPHP\Condorcet\Candidate|array|null``` +* [public Result->isProportional ()](Result%20Class/public%20Result--isProportional.md): ```bool``` + +### CondorcetPHP\Condorcet\Vote Class + +* [public Vote->__construct (...)](Vote%20Class/public%20Vote--__construct.md) +* [public Vote->addTags (...)](Vote%20Class/public%20Vote--addTags.md): ```bool``` +* [public Vote->countLinks ()](Vote%20Class/public%20Vote--countLinks.md): ```int``` +* [public Vote->countRankingCandidates ()](Vote%20Class/public%20Vote--countRankingCandidates.md): ```int``` +* [public Vote->getAllCandidates ()](Vote%20Class/public%20Vote--getAllCandidates.md): ```array``` +* [public Vote->getContextualRanking (...)](Vote%20Class/public%20Vote--getContextualRanking.md): ```array``` +* [public Vote->getContextualRankingAsString (...)](Vote%20Class/public%20Vote--getContextualRankingAsString.md): ```array``` +* [public Vote->getCreateTimestamp ()](Vote%20Class/public%20Vote--getCreateTimestamp.md): ```float``` +* [public Vote->getHashCode ()](Vote%20Class/public%20Vote--getHashCode.md): ```string``` +* [public Vote->getHistory ()](Vote%20Class/public%20Vote--getHistory.md): ```array``` +* [public Vote->getLinks ()](Vote%20Class/public%20Vote--getLinks.md): ```WeakMap``` +* [public Vote->getObjectVersion (...)](Vote%20Class/public%20Vote--getObjectVersion.md): ```string``` +* [public Vote->getRanking (...)](Vote%20Class/public%20Vote--getRanking.md): ```array``` +* [public Vote->getSimpleRanking (...)](Vote%20Class/public%20Vote--getSimpleRanking.md): ```string``` +* [public Vote->getTags ()](Vote%20Class/public%20Vote--getTags.md): ```array``` +* [public Vote->getTagsAsString ()](Vote%20Class/public%20Vote--getTagsAsString.md): ```string``` +* [public Vote->getTimestamp ()](Vote%20Class/public%20Vote--getTimestamp.md): ```float``` +* [public Vote->getWeight (...)](Vote%20Class/public%20Vote--getWeight.md): ```int``` +* [public Vote->haveLink (...)](Vote%20Class/public%20Vote--haveLink.md): ```bool``` +* [public Vote->removeAllTags ()](Vote%20Class/public%20Vote--removeAllTags.md): ```bool``` +* [public Vote->removeCandidate (...)](Vote%20Class/public%20Vote--removeCandidate.md): ```bool``` +* [public Vote->removeTags (...)](Vote%20Class/public%20Vote--removeTags.md): ```array``` +* [public Vote->setRanking (...)](Vote%20Class/public%20Vote--setRanking.md): ```bool``` +* [public Vote->setWeight (...)](Vote%20Class/public%20Vote--setWeight.md): ```int``` + +### CondorcetPHP\Condorcet\Algo\Pairwise Class + +* [public Algo\Pairwise->getExplicitPairwise ()](Algo_Pairwise%20Class/public%20Algo_Pairwise--getExplicitPairwise.md): ```array``` +* [public Algo\Pairwise->getObjectVersion (...)](Algo_Pairwise%20Class/public%20Algo_Pairwise--getObjectVersion.md): ```string``` + +### CondorcetPHP\Condorcet\Algo\Tools\Combinations Class + +* ```public static bool $useBigIntegerIfAvailable``` + + +### CondorcetPHP\Condorcet\Algo\Tools\Permutations Class + +* ```public static bool $useBigIntegerIfAvailable``` + + +### CondorcetPHP\Condorcet\Algo\Tools\StvQuotas Enum + +* ```case Algo\Tools\StvQuotas::DROOP``` +* ```case Algo\Tools\StvQuotas::HARE``` +* ```case Algo\Tools\StvQuotas::HAGENBACH_BISCHOFF``` +* ```case Algo\Tools\StvQuotas::IMPERIALI``` + +* [public static Algo\Tools\StvQuotas::make (...)](Algo_Tools_StvQuotas%20Class/public%20static%20Algo_Tools_StvQuotas--make.md): ```self``` + +### CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\PdoDriver\PdoHandlerDriver Class + +* ```public const SEGMENT: (array)``` + +* ```public static bool $preferBlobInsteadVarchar``` + + +### CondorcetPHP\Condorcet\Timer\Manager Class + +* [public Timer\Manager->getHistory ()](Timer_Manager%20Class/public%20Timer_Manager--getHistory.md): ```array``` +* [public Timer\Manager->getObjectVersion (...)](Timer_Manager%20Class/public%20Timer_Manager--getObjectVersion.md): ```string``` + +### CondorcetPHP\Condorcet\Tools\Converters\CondorcetElectionFormat Class + +* ```readonly public array $candidates``` +* ```readonly public int $numberOfSeats``` +* ```readonly public bool $implicitRanking``` +* ```readonly public bool $voteWeight``` +* ```readonly public bool $CandidatesParsedFromVotes``` +* ```readonly public int $invalidBlocksCount``` + +* [public static Tools\Converters\CondorcetElectionFormat::exportElectionToCondorcetElectionFormat (...)](Tools_Converters_CondorcetElectionFormat%20Class/public%20static%20Tools_Converters_CondorcetElectionFormat--exportElectionToCondorcetElectionFormat.md): ```?string``` +* [public Tools\Converters\CondorcetElectionFormat->__construct (...)](Tools_Converters_CondorcetElectionFormat%20Class/public%20Tools_Converters_CondorcetElectionFormat--__construct.md) +* [public Tools\Converters\CondorcetElectionFormat->setDataToAnElection (...)](Tools_Converters_CondorcetElectionFormat%20Class/public%20Tools_Converters_CondorcetElectionFormat--setDataToAnElection.md): ```CondorcetPHP\Condorcet\Election``` + +### CondorcetPHP\Condorcet\Tools\Converters\DavidHillFormat Class + +* ```readonly public array $candidates``` +* ```readonly public int $NumberOfSeats``` + +* [public Tools\Converters\DavidHillFormat->__construct (...)](Tools_Converters_DavidHillFormat%20Class/public%20Tools_Converters_DavidHillFormat--__construct.md) +* [public Tools\Converters\DavidHillFormat->setDataToAnElection (...)](Tools_Converters_DavidHillFormat%20Class/public%20Tools_Converters_DavidHillFormat--setDataToAnElection.md): ```CondorcetPHP\Condorcet\Election``` + +### CondorcetPHP\Condorcet\Tools\Converters\DebianFormat Class + +* ```readonly public array $candidates``` +* ```readonly public array $votes``` + +* [public Tools\Converters\DebianFormat->__construct (...)](Tools_Converters_DebianFormat%20Class/public%20Tools_Converters_DebianFormat--__construct.md) +* [public Tools\Converters\DebianFormat->setDataToAnElection (...)](Tools_Converters_DebianFormat%20Class/public%20Tools_Converters_DebianFormat--setDataToAnElection.md): ```CondorcetPHP\Condorcet\Election``` + +### CondorcetPHP\Condorcet\Utils\CondorcetUtil Class + +* [public static Utils\CondorcetUtil::format (...)](Utils_CondorcetUtil%20Class/public%20static%20Utils_CondorcetUtil--format.md): ```mixed``` + + + +# Full Class & Methods References +_Including above methods from public API_ + + +#### Abstract CondorcetPHP\Condorcet\Algo\Method +```php +* private const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Borda\BordaCount extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static int $optionStarting +* protected ?array $Stats +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getScoreByCandidateRanking (int $CandidatesRanked, CondorcetPHP\Condorcet\Election $election): float +* protected getStats (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Borda\DowdallSystem extends CondorcetPHP\Condorcet\Algo\Methods\Borda\BordaCount implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static int $optionStarting +* protected ?array $Stats +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getScoreByCandidateRanking (int $CandidatesRanked, CondorcetPHP\Condorcet\Election $election): float +* protected getStats (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\CondorcetBasic extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected ?int $CondorcetWinner +* protected ?int $CondorcetLoser +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getLoser (): ?int +* public getObjectVersion (bool $major = false): string +* public getResult (): never +* public getWinner (): ?int +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Copeland\Copeland extends CondorcetPHP\Condorcet\Algo\Methods\PairwiseStatsBased_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* protected const COUNT_TYPE: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* readonly protected array $Comparison +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected looking (array $challenge): int +* protected makeRanking (): void +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Dodgson\DodgsonQuick extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected ?array $Stats +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Dodgson\DodgsonTidemanApproximation extends CondorcetPHP\Condorcet\Algo\Methods\PairwiseStatsBased_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* protected const COUNT_TYPE: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* readonly protected array $Comparison +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected looking (array $challenge): int +* protected makeRanking (): void +``` + +#### Abstract CondorcetPHP\Condorcet\Algo\Methods\HighestAverages\HighestAverages_Core extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* final public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected array $candidatesVotes +* protected array $candidatesSeats +* protected array $rounds +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected computeQuotient (int $votesWeight, int $seats): float +* protected countVotesPerCandidates (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeRounds (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\HighestAverages\Jefferson extends CondorcetPHP\Condorcet\Algo\Methods\HighestAverages\HighestAverages_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* final public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected array $candidatesVotes +* protected array $candidatesSeats +* protected array $rounds +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected computeQuotient (int $votesWeight, int $seats): float +* protected countVotesPerCandidates (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeRounds (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\HighestAverages\SainteLague extends CondorcetPHP\Condorcet\Algo\Methods\HighestAverages\HighestAverages_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* final public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static int|float $optionFirstDivisor +* protected array $candidatesVotes +* protected array $candidatesSeats +* protected array $rounds +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected computeQuotient (int $votesWeight, int $seats): float +* protected countVotesPerCandidates (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeRounds (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\InstantRunoff\InstantRunoff extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected ?array $Stats +* readonly public float $majority +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeScore (array $candidateDone): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* final public const CONFLICT_WARNING_CODE: (integer) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static ?int $MaxCandidates +* readonly protected int $countElectionCandidates +* readonly protected array $candidatesKey +* readonly protected int $countPossibleRanking +* protected int $MaxScore +* protected int $Conflits +* protected int $bestRankingKey +* protected array $bestRankingTab +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected computeMaxAndConflicts (): void +* protected computeOneScore (array $ranking, CondorcetPHP\Condorcet\Algo\Pairwise $pairwise): int +* protected conflictInfos (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getPossibleRankingIterator (): Generator +* protected getStats (): array +* protected makeRanking (): void +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\LargestRemainder\LargestRemainder extends CondorcetPHP\Condorcet\Algo\Methods\HighestAverages\HighestAverages_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* final public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static CondorcetPHP\Condorcet\Algo\Tools\StvQuotas $optionQuota +* protected array $candidatesVotes +* protected array $candidatesSeats +* protected array $rounds +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected computeQuotient (int $votesWeight, int $seats): float +* protected countVotesPerCandidates (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeRounds (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Majority\FirstPastThePost extends CondorcetPHP\Condorcet\Algo\Methods\Majority\Majority_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected int $maxRound +* protected int $targetNumberOfCandidatesForTheNextRound +* protected int $numberOfTargetedCandidatesAfterEachRound +* protected array $admittedCandidates +* protected ?array $Stats +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected doOneRound (): array +* protected getStats (): array +``` + +#### Abstract CondorcetPHP\Condorcet\Algo\Methods\Majority\Majority_Core extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected int $maxRound +* protected int $targetNumberOfCandidatesForTheNextRound +* protected int $numberOfTargetedCandidatesAfterEachRound +* protected array $admittedCandidates +* protected ?array $Stats +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected doOneRound (): array +* protected getStats (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Majority\MultipleRoundsSystem extends CondorcetPHP\Condorcet\Algo\Methods\Majority\Majority_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected static int $optionMAX_ROUND +* protected static int $optionTARGET_NUMBER_OF_CANDIDATES_FOR_THE_NEXT_ROUND +* protected static int $optionNUMBER_OF_TARGETED_CANDIDATES_AFTER_EACH_ROUND +* protected int $maxRound +* protected int $targetNumberOfCandidatesForTheNextRound +* protected int $numberOfTargetedCandidatesAfterEachRound +* protected array $admittedCandidates +* protected ?array $Stats +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected doOneRound (): array +* protected getStats (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Minimax\MinimaxMargin extends CondorcetPHP\Condorcet\Algo\Methods\PairwiseStatsBased_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* protected const COUNT_TYPE: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* readonly protected array $Comparison +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected looking (array $challenge): int +* protected makeRanking (): void +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Minimax\MinimaxOpposition extends CondorcetPHP\Condorcet\Algo\Methods\PairwiseStatsBased_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* protected const COUNT_TYPE: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* readonly protected array $Comparison +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected looking (array $challenge): int +* protected makeRanking (): void +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Minimax\MinimaxWinning extends CondorcetPHP\Condorcet\Algo\Methods\PairwiseStatsBased_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* protected const COUNT_TYPE: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* readonly protected array $Comparison +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected looking (array $challenge): int +* protected makeRanking (): void +``` + +#### Abstract CondorcetPHP\Condorcet\Algo\Methods\PairwiseStatsBased_Core extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* private const COUNT_TYPE: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* readonly protected array $Comparison +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected looking (array $challenge): int +* protected makeRanking (): void +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\RankedPairs\RankedPairsMargin extends CondorcetPHP\Condorcet\Algo\Methods\RankedPairs\RankedPairs_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* protected const RP_VARIANT_1: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static ?int $MaxCandidates +* readonly protected array $PairwiseSort +* protected array $Arcs +* protected ?array $Stats +* protected bool $StatsDone +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected followCycle (array $virtualArcs, int $startCandidateKey, int $searchCandidateKey, array $done = []): array +* protected getArcsInCycle (array $virtualArcs): array +* protected getStats (): array +* protected getWinners (array $alreadyDone): array +* protected makeArcs (): void +* protected makeResult (): array +* protected pairwiseSort (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\RankedPairs\RankedPairsWinning extends CondorcetPHP\Condorcet\Algo\Methods\RankedPairs\RankedPairs_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* protected const RP_VARIANT_1: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static ?int $MaxCandidates +* readonly protected array $PairwiseSort +* protected array $Arcs +* protected ?array $Stats +* protected bool $StatsDone +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected followCycle (array $virtualArcs, int $startCandidateKey, int $searchCandidateKey, array $done = []): array +* protected getArcsInCycle (array $virtualArcs): array +* protected getStats (): array +* protected getWinners (array $alreadyDone): array +* protected makeArcs (): void +* protected makeResult (): array +* protected pairwiseSort (): array +``` + +#### Abstract CondorcetPHP\Condorcet\Algo\Methods\RankedPairs\RankedPairs_Core extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* protected const RP_VARIANT_1: (string) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static ?int $MaxCandidates +* readonly protected array $PairwiseSort +* protected array $Arcs +* protected ?array $Stats +* protected bool $StatsDone +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected followCycle (array $virtualArcs, int $startCandidateKey, int $searchCandidateKey, array $done = []): array +* protected getArcsInCycle (array $virtualArcs): array +* protected getStats (): array +* protected getWinners (array $alreadyDone): array +* protected makeArcs (): void +* protected makeResult (): array +* protected pairwiseSort (): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\STV\CPO_STV extends CondorcetPHP\Condorcet\Algo\Methods\STV\SingleTransferableVote implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const DEFAULT_METHODS_CHAINING: (array) +* final public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* public static ?int $MaxOutcomeComparisons +* public static CondorcetPHP\Condorcet\Algo\Tools\StvQuotas $optionQuota +* public static array $optionCondorcetCompletionMethod +* public static array $optionTieBreakerMethods +* protected ?array $Stats +* protected SplFixedArray $outcomes +* readonly protected array $initialScoreTable +* protected array $candidatesElectedFromFirstRound +* readonly protected array $candidatesEliminatedFromFirstRound +* protected SplFixedArray $outcomeComparisonTable +* readonly protected int $condorcetWinnerOutcome +* readonly protected array $completionMethodPairwise +* readonly protected CondorcetPHP\Condorcet\Result $completionMethodResult +* protected float $votesNeededToWin +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compareOutcomes (): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getOutcomesComparisonKey (int $MainOutcomeKey, int $ComparedOutcomeKey): string +* protected getStats (): array +* protected makeScore (array $surplus = [], array $candidateElected = [], array $candidateEliminated = []): array +* protected selectBestOutcome (): void +* protected sortResultBeforeCut (array $result): void +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\STV\SingleTransferableVote extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* final public const IS_PROPORTIONAL: (boolean) +* public const METHOD_NAME: (array) +* public const DECIMAL_PRECISION: (integer) + +* public static CondorcetPHP\Condorcet\Algo\Tools\StvQuotas $optionQuota +* protected ?array $Stats +* protected float $votesNeededToWin +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeScore (array $surplus = [], array $candidateElected = [], array $candidateEliminated = []): array +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Schulze\SchulzeMargin extends CondorcetPHP\Condorcet\Algo\Methods\Schulze\Schulze_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected array $StrongestPaths +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeRanking (): void +* protected makeStrongestPaths (): void +* protected prepareStrongestPath (): void +* protected schulzeVariant (int $i, int $j, CondorcetPHP\Condorcet\Election $election): int +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Schulze\SchulzeRatio extends CondorcetPHP\Condorcet\Algo\Methods\Schulze\Schulze_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected array $StrongestPaths +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeRanking (): void +* protected makeStrongestPaths (): void +* protected prepareStrongestPath (): void +* protected schulzeVariant (int $i, int $j, CondorcetPHP\Condorcet\Election $election): float +``` + +#### CondorcetPHP\Condorcet\Algo\Methods\Schulze\SchulzeWinning extends CondorcetPHP\Condorcet\Algo\Methods\Schulze\Schulze_Core implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const METHOD_NAME: (array) +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected array $StrongestPaths +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeRanking (): void +* protected makeStrongestPaths (): void +* protected prepareStrongestPath (): void +* protected schulzeVariant (int $i, int $j, CondorcetPHP\Condorcet\Election $election): int +``` + +#### Abstract CondorcetPHP\Condorcet\Algo\Methods\Schulze\Schulze_Core extends CondorcetPHP\Condorcet\Algo\Method implements CondorcetPHP\Condorcet\Algo\MethodInterface +```php +* public const IS_PROPORTIONAL: (boolean) +* public const DECIMAL_PRECISION: (integer) + +* protected array $StrongestPaths +* public static ?int $MaxCandidates +* readonly protected WeakReference $selfElection +* protected ?CondorcetPHP\Condorcet\Result $Result +* protected string $objectVersion + +* public static setOption (string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public __construct (CondorcetPHP\Condorcet\Election $mother) +* public __serialize (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getObjectVersion (bool $major = false): string +* public getResult (): CondorcetPHP\Condorcet\Result +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* protected compute (): void +* protected createResult (array $result): CondorcetPHP\Condorcet\Result +* protected getStats (): array +* protected makeRanking (): void +* protected makeStrongestPaths (): void +* protected prepareStrongestPath (): void +* protected schulzeVariant (int $i, int $j, CondorcetPHP\Condorcet\Election $election): int|float +``` + +#### CondorcetPHP\Condorcet\Algo\Pairwise implements ArrayAccess, Iterator, Traversable +```php +* private bool $valid +* protected WeakReference $Election +* protected array $Pairwise_Model +* protected array $Pairwise +* protected string $objectVersion + +* public __construct (CondorcetPHP\Condorcet\Election $link) +* public __serialize (): array +* public addNewVote (int $key): void +* public current (): array +* public getElection (): CondorcetPHP\Condorcet\Election +* public getExplicitPairwise (): array +* public getObjectVersion (bool $major = false): string +* public key (): ?int +* public next (): void +* public offsetExists (mixed $offset): bool +* public offsetGet (mixed $offset): ?array +* public offsetSet (mixed $offset, mixed $value): void +* public offsetUnset (mixed $offset): void +* public removeVote (int $key): void +* public rewind (): void +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* public valid (): bool +* protected computeOneVote (array $pairwise, CondorcetPHP\Condorcet\Vote $oneVote): void +* protected doPairwise (): void +* protected formatNewpairwise (): void +``` + +#### CondorcetPHP\Condorcet\Algo\StatsVerbosity implements UnitEnum, BackedEnum +```php +* ```case StatsVerbosity::NONE``` +* ```case StatsVerbosity::LOW``` +* ```case StatsVerbosity::STD``` +* ```case StatsVerbosity::HIGH``` +* ```case StatsVerbosity::FULL``` +* ```case StatsVerbosity::DEBUG``` + +* readonly public string $name +* readonly public int $value + +``` + +#### CondorcetPHP\Condorcet\Algo\Tools\Combinations +```php +* public static bool $useBigIntegerIfAvailable + +* public static compute (array $values, int $length, array $append_before = []): SplFixedArray +* public static computeGenerator (array $values, int $length, array $append_before = []): Generator +* public static getPossibleCountOfCombinations (int $count, int $length): int +``` + +#### Abstract CondorcetPHP\Condorcet\Algo\Tools\PairwiseStats +```php +* public static PairwiseComparison (CondorcetPHP\Condorcet\Algo\Pairwise $pairwise): array +``` + +#### CondorcetPHP\Condorcet\Algo\Tools\Permutations +```php +* public static bool $useBigIntegerIfAvailable +* readonly protected array $candidates + +* public static getPossibleCountOfPermutations (int $candidatesNumber): int +* public __construct (array $candidates) +* public getPermutationGenerator (): Generator +* public getResults (): SplFixedArray +* protected permutationGenerator (array $elements): Generator +``` + +#### CondorcetPHP\Condorcet\Algo\Tools\StvQuotas implements UnitEnum, BackedEnum +```php +* ```case StvQuotas::DROOP``` +* ```case StvQuotas::HARE``` +* ```case StvQuotas::HAGENBACH_BISCHOFF``` +* ```case StvQuotas::IMPERIALI``` + +* readonly public string $name +* readonly public string $value + +* public static make (string $quota): self +* public getQuota (int $votesWeight, int $seats): float +``` + +#### Abstract CondorcetPHP\Condorcet\Algo\Tools\TieBreakersCollection +```php +* public static electSomeLosersbasedOnPairwiseComparaison (CondorcetPHP\Condorcet\Election $election, array $candidatesKeys): array +* public static tieBreakerWithAnotherMethods (CondorcetPHP\Condorcet\Election $election, array $methods, array $candidatesKeys): array +``` + +#### Abstract CondorcetPHP\Condorcet\Algo\Tools\VirtualVote +```php +* public static removeCandidates (CondorcetPHP\Condorcet\Vote $vote, array $candidatesList): CondorcetPHP\Condorcet\Vote +``` + +#### CondorcetPHP\Condorcet\Benchmarks\AddVotesBench +```php +* protected CondorcetPHP\Condorcet\Election $election + +* public benchVotesWithManyCandidates (): void +``` + +#### CondorcetPHP\Condorcet\Benchmarks\IntensiveUsageBench +```php +* public benchSimpleManyVotes (): void +``` + +#### CondorcetPHP\Condorcet\Benchmarks\KemenyYoungBench +```php +* public __construct () +* public benchKemenyYoung (array $params): void +* public provideCandidatesCount (): Generator +``` + +#### CondorcetPHP\Condorcet\Benchmarks\MethodsNonProportionalBench +```php +* public bool $IS_A_PROPORTIONAL_BENCH +* public array $numberOfCandidates +* protected CondorcetPHP\Condorcet\Election $election + +* public __construct () +* public benchByCandidates (array $params): void +* public provideMethods (): Generator +* public provideNumberOfCandidates (): Generator +* public setUp (array $params): void +* protected buildElection (int $numberOfCandidates, int $numberOfVotes): void +``` + +#### CondorcetPHP\Condorcet\Benchmarks\MethodsProportionalBench extends CondorcetPHP\Condorcet\Benchmarks\MethodsNonProportionalBench +```php +* public bool $IS_A_PROPORTIONAL_BENCH +* public array $numberOfCandidates +* protected CondorcetPHP\Condorcet\Election $election + +* public __construct () +* public benchByCandidates (array $params): void +* public provideMethods (): Generator +* public provideNumberOfCandidates (): Generator +* public setUp (array $params): void +* protected buildElection (int $numberOfCandidates, int $numberOfVotes): void +``` + +#### CondorcetPHP\Condorcet\Benchmarks\PairwiseNumberOfCandidatesBench +```php +* public array $numberOfCandidates +* public array $numberOfVotes +* protected CondorcetPHP\Condorcet\Election $election + +* public __construct () +* public benchByCandidates (array $params): void +* public provideNumberOfCandidates (): Generator +* public provideNumberOfVotes (): Generator +* protected buildElection (int $numberOfCandidates, int $numberOfVotes): void +``` + +#### CondorcetPHP\Condorcet\Benchmarks\PairwiseUpdateOptimizationBench +```php +* public benchPairwiseOptimization (): void +``` + +#### CondorcetPHP\Condorcet\Benchmarks\SimpleUsageBench +```php +* public benchSimpleManyVotes (): void +``` + +#### CondorcetPHP\Condorcet\Candidate implements Stringable +```php +* private array $name +* private bool $provisional +* private ?WeakMap $link +* protected string $objectVersion + +* public __clone (): void +* public __construct (string $name) +* public __serialize (): array +* public __toString (): string +* public countLinks (): int +* public destroyLink (CondorcetPHP\Condorcet\Election $election): bool +* public getCreateTimestamp (): float +* public getHistory (): array +* public getLinks (): WeakMap +* public getName (): string +* public getObjectVersion (bool $major = false): string +* public getProvisionalState (): bool +* public getTimestamp (): float +* public haveLink (CondorcetPHP\Condorcet\Election $election): bool +* public registerLink (CondorcetPHP\Condorcet\Election $election): void +* public setName (string $name): bool +* public setProvisionalState (bool $provisional): void +* protected destroyAllLink (): void +* protected initWeakMap (): void +* private checkNameInElectionContext (string $name): bool +``` + +#### Abstract CondorcetPHP\Condorcet\Condorcet +```php +* final public const AUTHOR: (string) +* final public const HOMEPAGE: (string) +* final public const VERSION: (string) +* final public const CONDORCET_BASIC_CLASS: (string) + +* protected static ?string $defaultMethod +* protected static array $authMethods +* public static bool $UseTimer + +* public static addMethod (string $methodClass): bool +* public static condorcetBasicSubstitution (?string $substitution): string +* public static getAuthMethods (bool $basic = false): array +* public static getDefaultMethod (): ?string +* public static getMethodClass (string $method): ?string +* public static getVersion (bool $major = false): string +* public static isAuthMethod (string $method): bool +* public static setDefaultMethod (string $method): bool +* protected static testMethod (string $method): bool +``` + +#### CondorcetPHP\Condorcet\Console\Commands\ElectionCommand extends Symfony\Component\Console\Command\Command +```php +* public const SUCCESS: (integer) +* public const FAILURE: (integer) +* public const INVALID: (integer) + +* protected ?CondorcetPHP\Condorcet\Election $election +* protected ?string $candidates +* protected ?string $votes +* protected bool $displayMethodsStats +* protected ?string $CondorcetElectionFormatPath +* protected ?string $DebianFormatPath +* protected ?string $DavidHillFormatPath +* public static int $VotesPerMB +* protected string $iniMemoryLimit +* protected int $maxVotesInMemory +* protected bool $candidatesListIsWrite +* protected bool $votesCountIsWrite +* protected bool $pairwiseIsWrite +* public ?string $SQLitePath +* protected Symfony\Component\Console\Terminal $terminal +* protected CondorcetPHP\Condorcet\Console\Style\CondorcetStyle $io +* public static ?string $forceIniMemoryLimitTo +* protected CondorcetPHP\Condorcet\Timer\Manager $timer +* protected static $defaultName +* protected static $defaultDescription + +* public static getDefaultDescription (): ?string +* public static getDefaultName (): ?string +* public __construct (?string $name = null) +* public addArgument (string $name, ?int $mode = null, string $description = , mixed $default = null): static +* public addOption (string $name, array|string|null $shortcut = null, ?int $mode = null, string $description = , mixed $default = null): static +* public addUsage (string $usage): static +* public complete (Symfony\Component\Console\Completion\CompletionInput $input, Symfony\Component\Console\Completion\CompletionSuggestions $suggestions): void +* public getAliases (): array +* public getApplication (): ?Symfony\Component\Console\Application +* public getDefinition (): Symfony\Component\Console\Input\InputDefinition +* public getDescription (): string +* public getHelp (): string +* public getHelper (string $name): mixed +* public getHelperSet (): ?Symfony\Component\Console\Helper\HelperSet +* public getName (): ?string +* public getNativeDefinition (): Symfony\Component\Console\Input\InputDefinition +* public getProcessedHelp (): string +* public getSynopsis (bool $short = false): string +* public getUsages (): array +* public ignoreValidationErrors () +* public isEnabled () +* public isHidden (): bool +* public mergeApplicationDefinition (bool $mergeArgs = true) +* public run (Symfony\Component\Console\Input\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output): int +* public setAliases (iterable $aliases): static +* public setApplication (?Symfony\Component\Console\Application $application = null) +* public setCode (callable $code): static +* public setDefinition (Symfony\Component\Console\Input\InputDefinition|array $definition): static +* public setDescription (string $description): static +* public setHelp (string $help): static +* public setHelperSet (Symfony\Component\Console\Helper\HelperSet $helperSet) +* public setHidden (bool $hidden = true): static +* public setName (string $name): static +* public setProcessTitle (string $title): static +* protected configure (): void +* protected displayCandidatesList (Symfony\Component\Console\Output\OutputInterface $output): void +* protected displayConfigurationSection (): void +* protected displayDebugSection (): void +* protected displayDetailedElectionInputsSection (Symfony\Component\Console\Input\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output): void +* protected displayInputsSection (): void +* protected displayMethodsResultSection (Symfony\Component\Console\Input\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output): void +* protected displayNaturalCondorcet (Symfony\Component\Console\Input\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output): void +* protected displayPairwiseSection (Symfony\Component\Console\Output\OutputInterface $output): void +* protected displayTimerSection (): void +* protected displayVerbose (Symfony\Component\Console\Output\OutputInterface $output): void +* protected displayVotesCount (Symfony\Component\Console\Output\OutputInterface $output): void +* protected displayVotesList (Symfony\Component\Console\Output\OutputInterface $output): void +* protected execute (Symfony\Component\Console\Input\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output): int +* protected importInputsData (Symfony\Component\Console\Input\InputInterface $input): void +* protected initialize (Symfony\Component\Console\Input\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output): void +* protected interact (Symfony\Component\Console\Input\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output): void +* protected parseFromCandidatesArguments (): void +* protected parseFromCondorcetElectionFormat (Closure $callBack): void +* protected parseFromDavidHillFormat (): void +* protected parseFromDebianFormat (): void +* protected parseFromVotesArguments (Closure $callBack): void +* protected setUpParameters (Symfony\Component\Console\Input\InputInterface $input): void +* protected useDataHandler (Symfony\Component\Console\Input\InputInterface $input): ?Closure +``` + +#### Abstract CondorcetPHP\Condorcet\Console\CondorcetApplication +```php +* public static Symfony\Component\Console\Application $SymfonyConsoleApplication + +* public static create (): bool +* public static getVersionWithGitParsing (): string +* public static run (): void +``` + +#### Abstract CondorcetPHP\Condorcet\Console\Helper\CommandInputHelper +```php +* public static getFilePath (string $path): ?string +* public static pathIsAbsolute (string $path): bool +``` + +#### Abstract CondorcetPHP\Condorcet\Console\Helper\FormaterHelper +```php +* public static formatResultTable (CondorcetPHP\Condorcet\Result $result): array +* public static prepareMethods (array $methodArgument): array +``` + +#### CondorcetPHP\Condorcet\Console\Style\CondorcetStyle extends Symfony\Component\Console\Style\SymfonyStyle implements Symfony\Component\Console\Output\OutputInterface, Symfony\Component\Console\Style\StyleInterface +```php +* public const CONDORCET_MAIN_COLOR: (string) +* public const CONDORCET_SECONDARY_COLOR: (string) +* public const CONDORCET_THIRD_COLOR: (string) +* public const CONDORCET_WINNER_SYMBOL: (string) +* public const CONDORCET_LOSER_SYMBOL: (string) +* public const CONDORCET_WINNER_SYMBOL_FORMATED: (string) +* public const CONDORCET_LOSER_SYMBOL_FORMATED: (string) +* public const MAX_LINE_LENGTH: (integer) +* public const VERBOSITY_QUIET: (integer) +* public const VERBOSITY_NORMAL: (integer) +* public const VERBOSITY_VERBOSE: (integer) +* public const VERBOSITY_VERY_VERBOSE: (integer) +* public const VERBOSITY_DEBUG: (integer) +* public const OUTPUT_NORMAL: (integer) +* public const OUTPUT_RAW: (integer) +* public const OUTPUT_PLAIN: (integer) + +* readonly public Symfony\Component\Console\Helper\TableStyle $MainTableStyle +* readonly public Symfony\Component\Console\Helper\TableStyle $FirstColumnStyle + +* public __construct (Symfony\Component\Console\Input\InputInterface $input, Symfony\Component\Console\Output\OutputInterface $output) +* public ask (string $question, ?string $default = null, ?callable $validator = null): mixed +* public askHidden (string $question, ?callable $validator = null): mixed +* public askQuestion (Symfony\Component\Console\Question\Question $question): mixed +* public author (string $author): void +* public block (array|string $messages, ?string $type = null, ?string $style = null, string $prefix = , bool $padding = false, bool $escape = true) +* public caution (array|string $message) +* public choice (string $question, array $choices, mixed $default = null): mixed +* public choiceMultiple (string $question, array $choices, mixed $default, bool $multi): mixed +* public comment (array|string $message) +* public confirm (string $question, bool $default = true): bool +* public createProgressBar (int $max = 0): Symfony\Component\Console\Helper\ProgressBar +* public createTable (): Symfony\Component\Console\Helper\Table +* public definitionList (Symfony\Component\Console\Helper\TableSeparator|array|string $list) +* public error (array|string $message) +* public getErrorStyle (): self +* public getFormatter (): Symfony\Component\Console\Formatter\OutputFormatterInterface +* public getVerbosity (): int +* public homepage (string $homepage): void +* public horizontalTable (array $headers, array $rows) +* public info (array|string $message) +* public inlineSeparator (): void +* public instruction (string $prefix, string $message): void +* public isDebug (): bool +* public isDecorated (): bool +* public isQuiet (): bool +* public isVerbose (): bool +* public isVeryVerbose (): bool +* public listing (array $elements) +* public logo (int $terminalSize): void +* public methodResultSection (string $message): void +* public newLine (int $count = 1) +* public note (array|string $message): void +* public progressAdvance (int $step = 1) +* public progressFinish () +* public progressIterate (iterable $iterable, ?int $max = null): iterable +* public progressStart (int $max = 0) +* public section (string $message): void +* public setDecorated (bool $decorated) +* public setFormatter (Symfony\Component\Console\Formatter\OutputFormatterInterface $formatter) +* public setVerbosity (int $level) +* public success (array|string $message): void +* public table (array $headers, array $rows) +* public text (array|string $message) +* public title (string $message) +* public version (): void +* public warning (array|string $message) +* public write (iterable|string $messages, bool $newline = false, int $type = 1) +* public writeln (iterable|string $messages, int $type = 1) +* protected getErrorOutput () +``` + +#### CondorcetPHP\Condorcet\Constraints\NoTie implements CondorcetPHP\Condorcet\VoteConstraintInterface +```php +* public static isVoteAllow (CondorcetPHP\Condorcet\Election $election, CondorcetPHP\Condorcet\Vote $vote): bool +``` + +#### Abstract CondorcetPHP\Condorcet\DataManager\ArrayManager implements ArrayAccess, Countable, Iterator, Traversable +```php +* public static int $CacheSize +* public static int $MaxContainerLength +* protected array $Container +* protected ?CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface $DataHandler +* protected WeakReference $Election +* protected array $Cache +* protected int $CacheMaxKey +* protected int $CacheMinKey +* protected ?int $cursor +* protected int $counter +* protected int $maxKey +* protected bool $valid +* protected string $objectVersion + +* public __construct (CondorcetPHP\Condorcet\Election $election) +* public __destruct () +* public __serialize (): array +* public __unserialize (array $data): void +* public checkRegularize (): bool +* public clearCache (): void +* public closeHandler (): void +* public count (): int +* public current (): mixed +* public debugGetCache (): array +* public getCacheSize (): int +* public getContainerSize (): int +* public getElection (): CondorcetPHP\Condorcet\Election +* public getFirstKey (): int +* public getFullDataSet (): array +* public getObjectVersion (bool $major = false): string +* public importHandler (CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface $handler): bool +* public isUsingHandler (): bool +* public key (): ?int +* public keyExist (int $offset): bool +* public next (): void +* public offsetExists (mixed $offset): bool +* public offsetGet (mixed $offset): mixed +* public offsetSet (mixed $offset, mixed $value): void +* public offsetUnset (mixed $offset): void +* public regularize (): bool +* public resetCounter (): int +* public resetMaxKey (): ?int +* public rewind (): void +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* public valid (): bool +* protected decodeManyEntities (array $entities): array +* protected decodeOneEntity (string $data): CondorcetPHP\Condorcet\Vote +* protected encodeManyEntities (array $entities): array +* protected encodeOneEntity (CondorcetPHP\Condorcet\Vote $data): string +* protected populateCache (): void +* protected preDeletedTask (CondorcetPHP\Condorcet\Vote $object): void +* protected setCursorOnNextKeyInArray (array $array): void +``` + +#### CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\PdoDriver\PdoHandlerDriver implements CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface +```php +* public const SEGMENT: (array) + +* protected PDO $handler +* protected bool $transaction +* protected bool $queryError +* public static bool $preferBlobInsteadVarchar +* protected array $struct +* protected array $prepare +* protected string $objectVersion + +* public __construct (PDO $bdd, bool $tryCreateTable = false, array $struct = [Entities,id,data]) +* public closeTransaction (): void +* public countEntities (): int +* public createTable (): void +* public deleteOneEntity (int $key, bool $justTry): ?int +* public getObjectVersion (bool $major = false): string +* public insertEntities (array $input): void +* public selectMaxKey (): ?int +* public selectMinKey (): int +* public selectOneEntity (int $key): string|bool +* public selectRangeEntities (int $key, int $limit): array +* protected checkStructureTemplate (array $struct): bool +* protected initPrepareQuery (): void +* protected initTransaction (): void +* protected sliceInput (array $input): void +``` + +#### CondorcetPHP\Condorcet\DataManager\VotesManager extends CondorcetPHP\Condorcet\DataManager\ArrayManager implements Traversable, Iterator, Countable, ArrayAccess +```php +* public static int $CacheSize +* public static int $MaxContainerLength +* protected array $Container +* protected ?CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface $DataHandler +* protected WeakReference $Election +* protected array $Cache +* protected int $CacheMaxKey +* protected int $CacheMinKey +* protected ?int $cursor +* protected int $counter +* protected int $maxKey +* protected bool $valid +* protected string $objectVersion + +* public UpdateAndResetComputing (int $key, int $type): void +* public __construct (CondorcetPHP\Condorcet\Election $election) +* public __destruct () +* public __serialize (): array +* public __unserialize (array $data): void +* public checkRegularize (): bool +* public clearCache (): void +* public closeHandler (): void +* public count (): int +* public countInvalidVoteWithConstraints (): int +* public countVotes (?array $tag, bool $with): int +* public current (): mixed +* public debugGetCache (): array +* public getCacheSize (): int +* public getContainerSize (): int +* public getElection (): CondorcetPHP\Condorcet\Election +* public getFirstKey (): int +* public getFullDataSet (): array +* public getObjectVersion (bool $major = false): string +* public getVoteKey (CondorcetPHP\Condorcet\Vote $vote): ?int +* public getVotesList (?array $tags = null, bool $with = true): array +* public getVotesListAsString (bool $withContext): string +* public getVotesListGenerator (?array $tags = null, bool $with = true): Generator +* public getVotesValidUnderConstraintGenerator (?array $tags = null, bool $with = true): Generator +* public importHandler (CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface $handler): bool +* public isUsingHandler (): bool +* public key (): ?int +* public keyExist (int $offset): bool +* public next (): void +* public offsetExists (mixed $offset): bool +* public offsetGet (mixed $offset): CondorcetPHP\Condorcet\Vote +* public offsetSet (mixed $offset, mixed $value): void +* public offsetUnset (mixed $offset): void +* public regularize (): bool +* public resetCounter (): int +* public resetMaxKey (): ?int +* public rewind (): void +* public setElection (CondorcetPHP\Condorcet\Election $election): void +* public sumVotesWeight (bool $constraint = false): int +* public valid (): bool +* protected decodeManyEntities (array $entities): array +* protected decodeOneEntity (string $data): CondorcetPHP\Condorcet\Vote +* protected encodeManyEntities (array $entities): array +* protected encodeOneEntity (CondorcetPHP\Condorcet\Vote $data): string +* protected getFullVotesListGenerator (): Generator +* protected getPartialVotesListGenerator (array $tags, bool $with): Generator +* protected populateCache (): void +* protected preDeletedTask (CondorcetPHP\Condorcet\Vote $object): void +* protected setCursorOnNextKeyInArray (array $array): void +``` + +#### CondorcetPHP\Condorcet\Election +```php +* public const MAX_CANDIDATE_NAME_LENGTH: (integer) + +* protected static ?int $maxParseIteration +* protected static ?int $maxVoteNumber +* protected static bool $checksumMode +* protected CondorcetPHP\Condorcet\ElectionProcess\ElectionState $State +* protected CondorcetPHP\Condorcet\Timer\Manager $timer +* protected bool $ImplicitRanking +* protected bool $VoteWeightRule +* protected array $Constraints +* protected int $Seats +* protected string $objectVersion +* protected array $Candidates +* protected string $AutomaticNewCandidateName +* protected CondorcetPHP\Condorcet\DataManager\VotesManager $Votes +* protected int $voteFastMode +* protected ?CondorcetPHP\Condorcet\Algo\Pairwise $Pairwise +* protected ?array $Calculator +* protected CondorcetPHP\Condorcet\Algo\StatsVerbosity $StatsVerbosity + +* public static setMaxParseIteration (?int $maxParseIterations): ?int +* public static setMaxVoteNumber (?int $maxVotesNumber): ?int +* protected static formatResultOptions (array $arg): array +* public __clone (): void +* public __construct () +* public __serialize (): array +* public __unserialize (array $data): void +* public addCandidate (CondorcetPHP\Condorcet\Candidate|string|null $candidate = null): CondorcetPHP\Condorcet\Candidate +* public addCandidatesFromJson (string $input): array +* public addConstraint (string $constraintClass): bool +* public addVote (CondorcetPHP\Condorcet\Vote|array|string $vote, array|string|null $tags = null): CondorcetPHP\Condorcet\Vote +* public addVotesFromJson (string $input): int +* public allowsVoteWeight (bool $rule = true): bool +* public canAddCandidate (CondorcetPHP\Condorcet\Candidate|string $candidate): bool +* public checkVoteCandidate (CondorcetPHP\Condorcet\Vote $vote): bool +* public cleanupCalculator (): void +* public clearConstraints (): bool +* public computeResult (?string $method = null): void +* public convertRankingCandidates (array $ranking): bool +* public countCandidates (): int +* public countInvalidVoteWithConstraints (): int +* public countValidVoteWithConstraints (): int +* public countVotes (array|string|null $tags = null, bool $with = true): int +* public debugGetCalculator (): ?array +* public finishUpdateVote (CondorcetPHP\Condorcet\Vote $existVote): void +* public getCandidateKey (CondorcetPHP\Condorcet\Candidate|string $candidate): ?int +* public getCandidateObjectFromKey (int $candidate_key): ?CondorcetPHP\Condorcet\Candidate +* public getCandidateObjectFromName (string $candidateName): ?CondorcetPHP\Condorcet\Candidate +* public getCandidatesList (): array +* public getCandidatesListAsString (): array +* public getChecksum (): string +* public getCondorcetLoser (): ?CondorcetPHP\Condorcet\Candidate +* public getCondorcetWinner (): ?CondorcetPHP\Condorcet\Candidate +* public getConstraints (): array +* public getExplicitPairwise (): array +* public getGlobalTimer (): float +* public getImplicitRankingRule (): bool +* public getLastTimer (): float +* public getLoser (?string $method = null): CondorcetPHP\Condorcet\Candidate|array|null +* public getNumberOfSeats (): int +* public getObjectVersion (bool $major = false): string +* public getPairwise (): CondorcetPHP\Condorcet\Algo\Pairwise +* public getResult (?string $method = null, array $methodOptions = []): CondorcetPHP\Condorcet\Result +* public getState (): CondorcetPHP\Condorcet\ElectionProcess\ElectionState +* public getStatsVerbosity (): CondorcetPHP\Condorcet\Algo\StatsVerbosity +* public getTimerManager (): CondorcetPHP\Condorcet\Timer\Manager +* public getVoteKey (CondorcetPHP\Condorcet\Vote $vote): ?int +* public getVotesList (array|string|null $tags = null, bool $with = true): array +* public getVotesListAsString (bool $withContext = true): string +* public getVotesListGenerator (array|string|null $tags = null, bool $with = true): Generator +* public getVotesManager (): CondorcetPHP\Condorcet\DataManager\VotesManager +* public getVotesValidUnderConstraintGenerator (array|string|null $tags = null, bool $with = true): Generator +* public getWinner (?string $method = null): CondorcetPHP\Condorcet\Candidate|array|null +* public isRegisteredCandidate (CondorcetPHP\Condorcet\Candidate|string $candidate, bool $strictMode = true): bool +* public isVoteWeightAllowed (): bool +* public parseCandidates (string $input, bool $isFile = false): array +* public parseVotes (string $input, bool $isFile = false): int +* public parseVotesWithoutFail (SplFileInfo|string $input, bool $isFile = false, ?Closure $callBack = null): int +* public prepareUpdateVote (CondorcetPHP\Condorcet\Vote $existVote): void +* public removeCandidates (CondorcetPHP\Condorcet\Candidate|array|string $candidates_input): array +* public removeExternalDataHandler (): bool +* public removeVote (CondorcetPHP\Condorcet\Vote $vote): bool +* public removeVotesByTags (array|string $tags, bool $with = true): array +* public setExternalDataHandler (CondorcetPHP\Condorcet\DataManager\DataHandlerDrivers\DataHandlerDriverInterface $driver): bool +* public setImplicitRanking (bool $rule = true): bool +* public setMethodOption (string $method, string $optionName, BackedEnum|array|string|int|float $optionValue): bool +* public setNumberOfSeats (int $seats): int +* public setStateToVote (): bool +* public setStatsVerbosity (CondorcetPHP\Condorcet\Algo\StatsVerbosity $StatsVerbosity): void +* public sumValidVotesWeightWithConstraints (): int +* public sumVotesWeight (): int +* public testIfVoteIsValidUnderElectionConstraints (CondorcetPHP\Condorcet\Vote $vote): bool +* protected cleanupCompute (): void +* protected doAddVotesFromParse (array $adding): void +* protected initResult (string $class): void +* protected makePairwise (): void +* protected preparePairwiseAndCleanCompute (): bool +* protected prepareVoteInput (CondorcetPHP\Condorcet\Vote|array|string $vote, array|string|null $tags = null): void +* protected registerAllLinks (): void +* protected registerVote (CondorcetPHP\Condorcet\Vote $vote, array|string|null $tags): CondorcetPHP\Condorcet\Vote +* protected synthesisVoteFromParse (int $count, int $multiple, array $adding, CondorcetPHP\Condorcet\Vote|array|string $vote, array|string|null $tags, int $weight): void +``` + +#### CondorcetPHP\Condorcet\ElectionProcess\ElectionState implements UnitEnum, BackedEnum +```php +* ```case ElectionState::CANDIDATES_REGISTRATION``` +* ```case ElectionState::VOTES_REGISTRATION``` + +* readonly public string $name +* readonly public int $value + +``` + +#### CondorcetPHP\Condorcet\Result implements ArrayAccess, Countable, Iterator, Traversable +```php +* readonly protected array $Result +* protected array $ResultIterator +* protected $Stats +* protected array $warning +* readonly public array $ranking +* readonly public array $rankingAsString +* readonly public ?int $seats +* readonly public array $methodOptions +* readonly public ?CondorcetPHP\Condorcet\Candidate $CondorcetWinner +* readonly public ?CondorcetPHP\Condorcet\Candidate $CondorcetLoser +* readonly public float $buildTimestamp +* readonly public string $fromMethod +* readonly public string $byClass +* readonly public CondorcetPHP\Condorcet\Algo\StatsVerbosity $statsVerbosity +* readonly public string $electionCondorcetVersion +* protected string $objectVersion + +* public __construct (string $fromMethod, string $byClass, CondorcetPHP\Condorcet\Election $election, array $result, $stats, ?int $seats = null, array $methodOptions = []) +* public addWarning (int $type, ?string $msg = null): bool +* public count (): int +* public current (): CondorcetPHP\Condorcet\Candidate|array +* public getBuildTimeStamp (): float +* public getClassGenerator (): string +* public getCondorcetElectionGeneratorVersion (): string +* public getCondorcetLoser (): ?CondorcetPHP\Condorcet\Candidate +* public getCondorcetWinner (): ?CondorcetPHP\Condorcet\Candidate +* public getLoser (): CondorcetPHP\Condorcet\Candidate|array|null +* public getMethod (): string +* public getMethodOptions (): array +* public getNumberOfSeats (): ?int +* public getObjectVersion (bool $major = false): string +* public getOriginalResultArrayWithString (): array +* public getResultAsArray (bool $convertToString = false): array +* public getResultAsInternalKey (): array +* public getResultAsString (): string +* public getStats (): mixed +* public getWarning (?int $type = null): array +* public getWinner (): CondorcetPHP\Condorcet\Candidate|array|null +* public isProportional (): bool +* public key (): int +* public next (): void +* public offsetExists (mixed $offset): bool +* public offsetGet (mixed $offset): CondorcetPHP\Condorcet\Candidate|array|null +* public offsetSet (mixed $offset, mixed $value): void +* public offsetUnset (mixed $offset): void +* public rewind (): void +* public valid (): bool +* protected makeUserResult (CondorcetPHP\Condorcet\Election $election): array +``` + +#### CondorcetPHP\Condorcet\Throwable\AlgorithmException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\AlgorithmWithoutRankingFeatureException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\CandidateDoesNotExistException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\CandidateExistsException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\CandidateInvalidNameException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\CandidatesMaxNumberReachedException extends CondorcetPHP\Condorcet\Throwable\MethodLimitReachedException implements Stringable, Throwable +```php +* protected $message +* readonly public string $method +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string $method, int $maxCandidates) +* public getObjectVersion (bool $major = false): string +``` + +#### Abstract CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException extends Exception implements Stringable, Throwable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\DataHandlerException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\ElectionObjectVersionMismatchException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string $message = ) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\FileDoesNotExistException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\Internal\CondorcetInternalError extends Error implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\Internal\CondorcetInternalException extends Exception implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line + +``` + +#### CondorcetPHP\Condorcet\Throwable\Internal\IntegerOverflowException extends CondorcetPHP\Condorcet\Throwable\Internal\CondorcetInternalException implements Stringable, Throwable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line + +``` + +#### CondorcetPHP\Condorcet\Throwable\Internal\NoGitShellException extends CondorcetPHP\Condorcet\Throwable\Internal\CondorcetInternalException implements Stringable, Throwable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line + +``` + +#### CondorcetPHP\Condorcet\Throwable\MethodLimitReachedException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* readonly public string $method +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string $method, ?string $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\NoCandidatesException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\NoSeatsException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\ResultException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\ResultRequestedWithoutVotesException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\StvQuotaNotImplementedException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\TimerException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\VoteConstraintException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\VoteException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\VoteInvalidFormatException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\VoteManagerException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\VoteMaxNumberReachedException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\VoteNotLinkedException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Throwable\VotingHasStartedException extends CondorcetPHP\Condorcet\Throwable\CondorcetPublicApiException implements Throwable, Stringable +```php +* protected $message +* protected $code +* protected string $file +* protected int $line +* protected string $objectVersion + +* public __construct (string|int|null $message = null) +* public getObjectVersion (bool $major = false): string +``` + +#### CondorcetPHP\Condorcet\Timer\Chrono +```php +* protected CondorcetPHP\Condorcet\Timer\Manager $manager +* protected float $start +* protected ?string $role +* protected string $objectVersion + +* public __construct (CondorcetPHP\Condorcet\Timer\Manager $timer, ?string $role = null) +* public __destruct () +* public getObjectVersion (bool $major = false): string +* public getRole (): ?string +* public getStart (): float +* public getTimerManager (): CondorcetPHP\Condorcet\Timer\Manager +* public setRole (?string $role): void +* protected managerStartDeclare (): void +* protected resetStart (): void +``` + +#### CondorcetPHP\Condorcet\Timer\Manager +```php +* protected float $globalTimer +* protected ?float $lastTimer +* protected ?float $lastChronoTimestamp +* protected ?float $startDeclare +* protected array $history +* protected string $objectVersion + +* public addTime (CondorcetPHP\Condorcet\Timer\Chrono $chrono): void +* public getGlobalTimer (): float +* public getHistory (): array +* public getLastTimer (): float +* public getObjectVersion (bool $major = false): string +* public startDeclare (CondorcetPHP\Condorcet\Timer\Chrono $chrono): static +``` + +#### CondorcetPHP\Condorcet\Tools\Converters\CondorcetElectionFormat implements CondorcetPHP\Condorcet\Tools\Converters\ConverterInterface +```php +* public const SPECIAL_KEYWORD_EMPTY_RANKING: (string) +* protected const CANDIDATES_PATTERN: (string) +* protected const SEATS_PATTERN: (string) +* protected const IMPLICIT_PATTERN: (string) +* protected const WEIGHT_PATTERN: (string) + +* protected SplFileObject $file +* readonly public array $candidates +* readonly public int $numberOfSeats +* readonly public bool $implicitRanking +* readonly public bool $voteWeight +* readonly public bool $CandidatesParsedFromVotes +* readonly public int $invalidBlocksCount + +* public static exportElectionToCondorcetElectionFormat (CondorcetPHP\Condorcet\Election $election, bool $aggregateVotes = true, bool $includeNumberOfSeats = true, bool $includeTags = true, bool $inContext = false, ?SplFileObject $file = null): ?string +* public __construct (SplFileInfo|string $input) +* public setDataToAnElection (CondorcetPHP\Condorcet\Election $election = new CondorcetPHP\Condorcet\Election, ?Closure $callBack = null): CondorcetPHP\Condorcet\Election +* protected addCandidates (array $candidates): void +* protected boolParser (string $parse): bool +* protected parseCandidatesFromVotes (): void +* protected readParameters (): void +``` + +#### CondorcetPHP\Condorcet\Tools\Converters\DavidHillFormat implements CondorcetPHP\Condorcet\Tools\Converters\ConverterInterface +```php +* protected array $lines +* readonly public array $candidates +* readonly public int $NumberOfSeats + +* public __construct (string $filePath) +* public setDataToAnElection (?CondorcetPHP\Condorcet\Election $election = null): CondorcetPHP\Condorcet\Election +* protected readCandidatesNames (): void +* protected readNumberOfSeats (): void +* protected readVotes (): void +``` + +#### CondorcetPHP\Condorcet\Tools\Converters\DebianFormat implements CondorcetPHP\Condorcet\Tools\Converters\ConverterInterface +```php +* protected array $lines +* readonly public array $candidates +* readonly public array $votes + +* public __construct (string $filePath) +* public setDataToAnElection (?CondorcetPHP\Condorcet\Election $election = null): CondorcetPHP\Condorcet\Election +* protected readCandidatesNames (): void +* protected readVotes (): void +``` + +#### Abstract CondorcetPHP\Condorcet\Utils\CondorcetUtil +```php +* public static format (mixed $input, bool $convertObject = true): mixed +* public static isValidJsonForCondorcet (string $string): void +* public static prepareJson (string $input): mixed +* public static prepareParse (string $input, bool $isFile): array +``` + +#### CondorcetPHP\Condorcet\Utils\VoteEntryParser +```php +* readonly public string $originalEntry +* readonly public ?string $comment +* readonly public int $multiple +* readonly public ?array $ranking +* readonly public ?array $tags +* readonly public int $weight + +* public static convertRankingFromString (string $formula): ?array +* public static convertTagsFromVoteString (string $voteString, bool $cut = false): ?array +* public static getComment (string $voteString, bool $cut = false): ?string +* public static parseIntValueFromVoteStringOffset (string $character, string $entry, bool $cut = false): int +* public __construct (string $entry) +``` + +#### Abstract CondorcetPHP\Condorcet\Utils\VoteUtil +```php +* public static getRankingAsString (array $ranking): string +* public static tagsConvert (array|string|null $tags): ?array +``` + +#### CondorcetPHP\Condorcet\Vote implements Iterator, Stringable, Traversable +```php +* private int $position +* private array $ranking +* private float $lastTimestamp +* private int $counter +* private array $ranking_history +* private int $weight +* private array $tags +* private string $hashCode +* private ?CondorcetPHP\Condorcet\Election $electionContext +* public bool $notUpdate +* protected static ?stdClass $cacheKey +* protected ?WeakMap $cacheMap +* private ?WeakMap $link +* protected string $objectVersion + +* public static clearCache (): void +* public static initCache (): stdClass +* public __clone (): void +* public __construct (array|string $ranking, array|string|null $tags = null, ?float $ownTimestamp = null, ?CondorcetPHP\Condorcet\Election $electionContext = null) +* public __serialize (): array +* public __toString (): string +* public __wakeup (): void +* public addTags (array|string $tags): bool +* public countLinks (): int +* public countRankingCandidates (): int +* public current (): array +* public destroyLink (CondorcetPHP\Condorcet\Election $election): bool +* public getAllCandidates (): array +* public getContextualRanking (CondorcetPHP\Condorcet\Election $election): array +* public getContextualRankingAsString (CondorcetPHP\Condorcet\Election $election): array +* public getContextualRankingWithoutSort (CondorcetPHP\Condorcet\Election $election): array +* public getCreateTimestamp (): float +* public getHashCode (): string +* public getHistory (): array +* public getLinks (): WeakMap +* public getObjectVersion (bool $major = false): string +* public getRanking (bool $sortCandidatesInRank = true): array +* public getSimpleRanking (?CondorcetPHP\Condorcet\Election $context = null, bool $displayWeight = true): string +* public getTags (): array +* public getTagsAsString (): string +* public getTimestamp (): float +* public getWeight (?CondorcetPHP\Condorcet\Election $context = null): int +* public haveLink (CondorcetPHP\Condorcet\Election $election): bool +* public key (): int +* public next (): void +* public registerLink (CondorcetPHP\Condorcet\Election $election): void +* public removeAllTags (): bool +* public removeCandidate (CondorcetPHP\Condorcet\Candidate|string $candidate): bool +* public removeTags (array|string $tags): array +* public rewind (): void +* public setRanking (array|string $ranking, ?float $ownTimestamp = null): bool +* public setWeight (int $newWeight): int +* public valid (): bool +* protected computeContextualRanking (CondorcetPHP\Condorcet\Election $election, bool $sortLastRankByName): array +* protected computeContextualRankingWithoutImplicit (array $ranking, CondorcetPHP\Condorcet\Election $election, int $countContextualCandidate = 0): array +* protected destroyAllLink (): void +* protected initWeakMap (): void +* private archiveRanking (): void +* private computeHashCode (): string +* private formatRanking (array|string $ranking): int +``` diff --git a/include/Condorcet/Documentation/Result Class/public Result--getBuildTimeStamp.md b/include/Condorcet/Documentation/Result Class/public Result--getBuildTimeStamp.md new file mode 100644 index 0000000..f147235 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getBuildTimeStamp.md @@ -0,0 +1,15 @@ +## public Result::getBuildTimeStamp + +### Description + +```php +public Result->getBuildTimeStamp ( ): float +``` + +Get the timestamp of this result. + + +### Return value: + +*(```float```)* Microsecond timestamp. + diff --git a/include/Condorcet/Documentation/Result Class/public Result--getClassGenerator.md b/include/Condorcet/Documentation/Result Class/public Result--getClassGenerator.md new file mode 100644 index 0000000..f21f378 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getClassGenerator.md @@ -0,0 +1,21 @@ +## public Result::getClassGenerator + +### Description + +```php +public Result->getClassGenerator ( ): string +``` + +Get the The algorithmic method used for this result. + + +### Return value: + +*(```string```)* Method class path like CondorcetPHP\Condorcet\Algo\Methods\Copeland + + +--------------------------------------- + +### Related method(s) + +* [Result::getMethod](../Result%20Class/public%20Result--getMethod.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getCondorcetElectionGeneratorVersion.md b/include/Condorcet/Documentation/Result Class/public Result--getCondorcetElectionGeneratorVersion.md new file mode 100644 index 0000000..baec58f --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getCondorcetElectionGeneratorVersion.md @@ -0,0 +1,15 @@ +## public Result::getCondorcetElectionGeneratorVersion + +### Description + +```php +public Result->getCondorcetElectionGeneratorVersion ( ): string +``` + +Get the Condorcet PHP version that build this Result. + + +### Return value: + +*(```string```)* Condorcet PHP version string format. + diff --git a/include/Condorcet/Documentation/Result Class/public Result--getCondorcetLoser.md b/include/Condorcet/Documentation/Result Class/public Result--getCondorcetLoser.md new file mode 100644 index 0000000..a634719 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getCondorcetLoser.md @@ -0,0 +1,22 @@ +## public Result::getCondorcetLoser + +### Description + +```php +public Result->getCondorcetLoser ( ): ?CondorcetPHP\Condorcet\Candidate +``` + +Get the Condorcet loser, if exist, at the result time. + + +### Return value: + +*(```?CondorcetPHP\Condorcet\Candidate```)* Condorcet/Candidate object if there is a Condorcet loser or NULL instead. + + +--------------------------------------- + +### Related method(s) + +* [Result::getCondorcetWinner](../Result%20Class/public%20Result--getCondorcetWinner.md) +* [Election::getLoser](../Election%20Class/public%20Election--getLoser.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getCondorcetWinner.md b/include/Condorcet/Documentation/Result Class/public Result--getCondorcetWinner.md new file mode 100644 index 0000000..eae1bfa --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getCondorcetWinner.md @@ -0,0 +1,22 @@ +## public Result::getCondorcetWinner + +### Description + +```php +public Result->getCondorcetWinner ( ): ?CondorcetPHP\Condorcet\Candidate +``` + +Get the Condorcet winner, if exist, at the result time. + + +### Return value: + +*(```?CondorcetPHP\Condorcet\Candidate```)* CondorcetPHP\Condorcet\Candidate object if there is a Condorcet winner or NULL instead. + + +--------------------------------------- + +### Related method(s) + +* [Result::getCondorcetLoser](../Result%20Class/public%20Result--getCondorcetLoser.md) +* [Election::getWinner](../Election%20Class/public%20Election--getWinner.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getLoser.md b/include/Condorcet/Documentation/Result Class/public Result--getLoser.md new file mode 100644 index 0000000..863c637 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getLoser.md @@ -0,0 +1,23 @@ +## public Result::getLoser + +### Description + +```php +public Result->getLoser ( ): CondorcetPHP\Condorcet\Candidate|array|null +``` + +Equivalent to [Condorcet/Election::getWinner($method)](../Election Class/public Election--getWinner.md). + + +### Return value: + +*(```CondorcetPHP\Condorcet\Candidate|array|null```)* Candidate object given. Null if there are no available loser. +You can get an array with multiples losers. + + +--------------------------------------- + +### Related method(s) + +* [Result::getWinner](../Result%20Class/public%20Result--getWinner.md) +* [Election::getLoser](../Election%20Class/public%20Election--getLoser.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getMethod.md b/include/Condorcet/Documentation/Result Class/public Result--getMethod.md new file mode 100644 index 0000000..76c495d --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getMethod.md @@ -0,0 +1,21 @@ +## public Result::getMethod + +### Description + +```php +public Result->getMethod ( ): string +``` + +Get the The algorithmic method used for this result. + + +### Return value: + +*(```string```)* Method name. + + +--------------------------------------- + +### Related method(s) + +* [Result::getClassGenerator](../Result%20Class/public%20Result--getClassGenerator.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getMethodOptions.md b/include/Condorcet/Documentation/Result Class/public Result--getMethodOptions.md new file mode 100644 index 0000000..80efe96 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getMethodOptions.md @@ -0,0 +1,21 @@ +## public Result::getMethodOptions + +### Description + +```php +public Result->getMethodOptions ( ): array +``` + +Return the method options. + + +### Return value: + +*(```array```)* Array of options. Can be empty for most of the methods. + + +--------------------------------------- + +### Related method(s) + +* [Result::getClassGenerator](../Result%20Class/public%20Result--getClassGenerator.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getNumberOfSeats.md b/include/Condorcet/Documentation/Result Class/public Result--getNumberOfSeats.md new file mode 100644 index 0000000..6275df1 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getNumberOfSeats.md @@ -0,0 +1,22 @@ +## public Result::getNumberOfSeats + +### Description + +```php +public Result->getNumberOfSeats ( ): ?int +``` + +Get number of Seats for STV methods result. + + +### Return value: + +*(```?int```)* Number of seats if this result is a STV method. Else NULL. + + +--------------------------------------- + +### Related method(s) + +* [Election::setNumberOfSeats](../Election%20Class/public%20Election--setNumberOfSeats.md) +* [Election::getNumberOfSeats](../Election%20Class/public%20Election--getNumberOfSeats.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getObjectVersion.md b/include/Condorcet/Documentation/Result Class/public Result--getObjectVersion.md new file mode 100644 index 0000000..f6fa06f --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getObjectVersion.md @@ -0,0 +1,25 @@ +## public Result::getObjectVersion + +### Description + +```php +public Result->getObjectVersion ( [bool $major = false] ): string +``` + +Get the Condorcet PHP version who built this Election object. Usefull pour serializing Election. + + +##### **major:** *```bool```* +true will return : '2.0' and false will return : '2.0.0'. + + +### Return value: + +*(```string```)* Condorcet PHP version. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getVersion](../Condorcet%20Class/public%20static%20Condorcet--getVersion.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getOriginalResultArrayWithString.md b/include/Condorcet/Documentation/Result Class/public Result--getOriginalResultArrayWithString.md new file mode 100644 index 0000000..e0a61a5 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getOriginalResultArrayWithString.md @@ -0,0 +1,25 @@ +## public Result::getOriginalResultArrayWithString + +### Description + +```php +public Result->getOriginalResultArrayWithString ( ): array +``` + +Get result as an array + + +### Return value: + +*(```array```)* Unlike other methods to recover the result. This is frozen as soon as the original creation of the Result object is created. +Candidate objects are therefore protected from any change of candidateName, since the candidate objects are converted into a string when the results are promulgated. + +This control method can therefore be useful if you undertake suspicious operations on candidate objects after the results have been promulgated. + + +--------------------------------------- + +### Related method(s) + +* [Result::getResultAsArray](../Result%20Class/public%20Result--getResultAsArray.md) +* [Result::getResultAsString](../Result%20Class/public%20Result--getResultAsString.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getResultAsArray.md b/include/Condorcet/Documentation/Result Class/public Result--getResultAsArray.md new file mode 100644 index 0000000..e3609f3 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getResultAsArray.md @@ -0,0 +1,26 @@ +## public Result::getResultAsArray + +### Description + +```php +public Result->getResultAsArray ( [bool $convertToString = false] ): array +``` + +Get result as an array + + +##### **convertToString:** *```bool```* +Convert Candidate object to string. + + +### Return value: + +*(```array```)* An ordered multidimensionnal array by rank. + + +--------------------------------------- + +### Related method(s) + +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) +* [Result::getResultAsString](../Result%20Class/public%20Result--getResultAsString.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getResultAsString.md b/include/Condorcet/Documentation/Result Class/public Result--getResultAsString.md new file mode 100644 index 0000000..3de4d93 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getResultAsString.md @@ -0,0 +1,22 @@ +## public Result::getResultAsString + +### Description + +```php +public Result->getResultAsString ( ): string +``` + +Get result as string + + +### Return value: + +*(```string```)* Result ranking as string. + + +--------------------------------------- + +### Related method(s) + +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) +* [Result::getResultAsArray](../Result%20Class/public%20Result--getResultAsArray.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--getStats.md b/include/Condorcet/Documentation/Result Class/public Result--getStats.md new file mode 100644 index 0000000..1d8d240 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getStats.md @@ -0,0 +1,27 @@ +## public Result::getStats + +### Description + +```php +public Result->getStats ( ): mixed +``` + +Get advanced computing data from used algorithm. Like Strongest paths for Schulze method. + + +### Return value: + +*(```mixed```)* Varying according to the algorithm used. + + +--------------------------------------- + +### Related method(s) + +* [Election::getResult](../Election%20Class/public%20Election--getResult.md) + +--------------------------------------- + +### Examples and explanation + +* **[Advanced Result Management](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-3.-Advanced-Results-Management)** diff --git a/include/Condorcet/Documentation/Result Class/public Result--getWarning.md b/include/Condorcet/Documentation/Result Class/public Result--getWarning.md new file mode 100644 index 0000000..9f9418e --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getWarning.md @@ -0,0 +1,19 @@ +## public Result::getWarning + +### Description + +```php +public Result->getWarning ( [?int $type = null] ): array +``` + +From native methods: only Kemeny-Young use it to inform about a conflict during the computation process. + + +##### **type:** *```?int```* +Filter on a specific warning type code. + + +### Return value: + +*(```array```)* Warnings provided by the by the method that generated the warning. Empty array if there is not. + diff --git a/include/Condorcet/Documentation/Result Class/public Result--getWinner.md b/include/Condorcet/Documentation/Result Class/public Result--getWinner.md new file mode 100644 index 0000000..9a3fa46 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--getWinner.md @@ -0,0 +1,23 @@ +## public Result::getWinner + +### Description + +```php +public Result->getWinner ( ): CondorcetPHP\Condorcet\Candidate|array|null +``` + +Equivalent to [Condorcet/Election::getWinner($method)](../Election Class/public Election--getWinner.md). + + +### Return value: + +*(```CondorcetPHP\Condorcet\Candidate|array|null```)* Candidate object given. Null if there are no available winner. +You can get an array with multiples winners. + + +--------------------------------------- + +### Related method(s) + +* [Result::getLoser](../Result%20Class/public%20Result--getLoser.md) +* [Election::getWinner](../Election%20Class/public%20Election--getWinner.md) diff --git a/include/Condorcet/Documentation/Result Class/public Result--isProportional.md b/include/Condorcet/Documentation/Result Class/public Result--isProportional.md new file mode 100644 index 0000000..d14f9c6 --- /dev/null +++ b/include/Condorcet/Documentation/Result Class/public Result--isProportional.md @@ -0,0 +1,15 @@ +## public Result::isProportional + +### Description + +```php +public Result->isProportional ( ): bool +``` + +Does the result come from a proportional method + +--------------------------------------- + +### Related method(s) + +* [Result::getNumberOfSeats](../Result%20Class/public%20Result--getNumberOfSeats.md) diff --git a/include/Condorcet/Documentation/Timer_Manager Class/public Timer_Manager--getHistory.md b/include/Condorcet/Documentation/Timer_Manager Class/public Timer_Manager--getHistory.md new file mode 100644 index 0000000..80b5156 --- /dev/null +++ b/include/Condorcet/Documentation/Timer_Manager Class/public Timer_Manager--getHistory.md @@ -0,0 +1,21 @@ +## public Timer\Manager::getHistory + +### Description + +```php +public Timer\Manager->getHistory ( ): array +``` + +Return benchmarked actions history. + + +### Return value: + +*(```array```)* An explicit array with history. + + +--------------------------------------- + +### Related method(s) + +* [Election::getTimerManager](../Election%20Class/public%20Election--getTimerManager.md) diff --git a/include/Condorcet/Documentation/Timer_Manager Class/public Timer_Manager--getObjectVersion.md b/include/Condorcet/Documentation/Timer_Manager Class/public Timer_Manager--getObjectVersion.md new file mode 100644 index 0000000..8ab533c --- /dev/null +++ b/include/Condorcet/Documentation/Timer_Manager Class/public Timer_Manager--getObjectVersion.md @@ -0,0 +1,25 @@ +## public Timer\Manager::getObjectVersion + +### Description + +```php +public Timer\Manager->getObjectVersion ( [bool $major = false] ): string +``` + +Get the Condorcet PHP version who built this Election object. Usefull pour serializing Election. + + +##### **major:** *```bool```* +true will return : '2.0' and false will return : '2.0.0'. + + +### Return value: + +*(```string```)* Condorcet PHP version. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getVersion](../Condorcet%20Class/public%20static%20Condorcet--getVersion.md) diff --git a/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public Tools_Converters_CondorcetElectionFormat--__construct.md b/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public Tools_Converters_CondorcetElectionFormat--__construct.md new file mode 100644 index 0000000..eda5273 --- /dev/null +++ b/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public Tools_Converters_CondorcetElectionFormat--__construct.md @@ -0,0 +1,13 @@ +## public Tools\Converters\CondorcetElectionFormat::__construct + +### Description + +```php +public Tools\Converters\CondorcetElectionFormat->__construct ( SplFileInfo|string $input ) +``` + +Read a Condorcet format file, usually using .cvotes file extension + + +##### **input:** *```SplFileInfo|string```* +String, valid path to a text file or an object SplFileInfo or extending it like SplFileObject. diff --git a/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public Tools_Converters_CondorcetElectionFormat--setDataToAnElection.md b/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public Tools_Converters_CondorcetElectionFormat--setDataToAnElection.md new file mode 100644 index 0000000..d90d5fa --- /dev/null +++ b/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public Tools_Converters_CondorcetElectionFormat--setDataToAnElection.md @@ -0,0 +1,30 @@ +## public Tools\Converters\CondorcetElectionFormat::setDataToAnElection + +### Description + +```php +public Tools\Converters\CondorcetElectionFormat->setDataToAnElection ( [CondorcetPHP\Condorcet\Election $election = new CondorcetPHP\Condorcet\Election , ?Closure $callBack = null] ): CondorcetPHP\Condorcet\Election +``` + +Add the data to an election object + + +##### **election:** *```CondorcetPHP\Condorcet\Election```* +Add an existing election, useful if you want to set up some parameters or add extra candidates. If null an election object will be created for you. + + +##### **callBack:** *```?Closure```* +Callback function to execute after each registered vote. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Election```)* The election object + + +--------------------------------------- + +### Related method(s) + +* [Tools\DavidHillFormat::setDataToAnElection](../Tools\DavidHillFormat%20Class/public%20Tools\DavidHillFormat--setDataToAnElection.md) +* [Tools\DebianFormat::setDataToAnElection](../Tools\DebianFormat%20Class/public%20Tools\DebianFormat--setDataToAnElection.md) diff --git a/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public static Tools_Converters_CondorcetElectionFormat--exportElectionToCondorcetElectionFormat.md b/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public static Tools_Converters_CondorcetElectionFormat--exportElectionToCondorcetElectionFormat.md new file mode 100644 index 0000000..884c63a --- /dev/null +++ b/include/Condorcet/Documentation/Tools_Converters_CondorcetElectionFormat Class/public static Tools_Converters_CondorcetElectionFormat--exportElectionToCondorcetElectionFormat.md @@ -0,0 +1,40 @@ +## public static Tools\Converters\CondorcetElectionFormat::exportElectionToCondorcetElectionFormat + +### Description + +```php +public static Tools\Converters\CondorcetElectionFormat::exportElectionToCondorcetElectionFormat ( CondorcetPHP\Condorcet\Election $election [, bool $aggregateVotes = true , bool $includeNumberOfSeats = true , bool $includeTags = true , bool $inContext = false , ?SplFileObject $file = null] ): ?string +``` + +Create a CondorcetElectionFormat file from an Election object. + + + +##### **election:** *```CondorcetPHP\Condorcet\Election```* +Election with data. + + +##### **aggregateVotes:** *```bool```* +If true, will try to reduce number of lines, with quantifier for identical votes. + + +##### **includeNumberOfSeats:** *```bool```* +Add the Number Of Seats parameters to the output. + + +##### **includeTags:** *```bool```* +Add the vote tags information if any. Don't work if $aggregateVotes is true. + + +##### **inContext:** *```bool```* +Non-election candidates will be ignored. If the implicit ranking parameter of the election object is true, the last rank will also be provided to facilitate the reading. + + +##### **file:** *```?SplFileObject```* +If provided, the function will return null and the result will be writing directly to the file instead. _Note that the file cursor is not rewinding_. + + +### Return value: + +*(```?string```)* If the file is not provided, it's return a CondorcetElectionFormat as string, else returning null and working directly on the file object (necessary for very large non-aggregated elections, at the risk of memory saturation). + diff --git a/include/Condorcet/Documentation/Tools_Converters_DavidHillFormat Class/public Tools_Converters_DavidHillFormat--__construct.md b/include/Condorcet/Documentation/Tools_Converters_DavidHillFormat Class/public Tools_Converters_DavidHillFormat--__construct.md new file mode 100644 index 0000000..34a8878 --- /dev/null +++ b/include/Condorcet/Documentation/Tools_Converters_DavidHillFormat Class/public Tools_Converters_DavidHillFormat--__construct.md @@ -0,0 +1,13 @@ +## public Tools\Converters\DavidHillFormat::__construct + +### Description + +```php +public Tools\Converters\DavidHillFormat->__construct ( string $filePath ) +``` + +Read a Tideman format file + + +##### **filePath:** *```string```* +File absolute path. diff --git a/include/Condorcet/Documentation/Tools_Converters_DavidHillFormat Class/public Tools_Converters_DavidHillFormat--setDataToAnElection.md b/include/Condorcet/Documentation/Tools_Converters_DavidHillFormat Class/public Tools_Converters_DavidHillFormat--setDataToAnElection.md new file mode 100644 index 0000000..a9424a5 --- /dev/null +++ b/include/Condorcet/Documentation/Tools_Converters_DavidHillFormat Class/public Tools_Converters_DavidHillFormat--setDataToAnElection.md @@ -0,0 +1,26 @@ +## public Tools\Converters\DavidHillFormat::setDataToAnElection + +### Description + +```php +public Tools\Converters\DavidHillFormat->setDataToAnElection ( [?CondorcetPHP\Condorcet\Election $election = null] ): CondorcetPHP\Condorcet\Election +``` + +Add the data to an election object + + +##### **election:** *```?CondorcetPHP\Condorcet\Election```* +Add an existing election, useful if you want to set up some parameters or add extra candidates. If null an election object will be created for you. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Election```)* The election object + + +--------------------------------------- + +### Related method(s) + +* [Tools\CondorcetElectionFormat::setDataToAnElection](../Tools\CondorcetElectionFormat%20Class/public%20Tools\CondorcetElectionFormat--setDataToAnElection.md) +* [Tools\DebianFormat::setDataToAnElection](../Tools\DebianFormat%20Class/public%20Tools\DebianFormat--setDataToAnElection.md) diff --git a/include/Condorcet/Documentation/Tools_Converters_DebianFormat Class/public Tools_Converters_DebianFormat--__construct.md b/include/Condorcet/Documentation/Tools_Converters_DebianFormat Class/public Tools_Converters_DebianFormat--__construct.md new file mode 100644 index 0000000..fcc29e4 --- /dev/null +++ b/include/Condorcet/Documentation/Tools_Converters_DebianFormat Class/public Tools_Converters_DebianFormat--__construct.md @@ -0,0 +1,13 @@ +## public Tools\Converters\DebianFormat::__construct + +### Description + +```php +public Tools\Converters\DebianFormat->__construct ( string $filePath ) +``` + +Read a Tideman format file + + +##### **filePath:** *```string```* +File absolute path. diff --git a/include/Condorcet/Documentation/Tools_Converters_DebianFormat Class/public Tools_Converters_DebianFormat--setDataToAnElection.md b/include/Condorcet/Documentation/Tools_Converters_DebianFormat Class/public Tools_Converters_DebianFormat--setDataToAnElection.md new file mode 100644 index 0000000..babab7c --- /dev/null +++ b/include/Condorcet/Documentation/Tools_Converters_DebianFormat Class/public Tools_Converters_DebianFormat--setDataToAnElection.md @@ -0,0 +1,26 @@ +## public Tools\Converters\DebianFormat::setDataToAnElection + +### Description + +```php +public Tools\Converters\DebianFormat->setDataToAnElection ( [?CondorcetPHP\Condorcet\Election $election = null] ): CondorcetPHP\Condorcet\Election +``` + +Add the Debian data to an election object + + +##### **election:** *```?CondorcetPHP\Condorcet\Election```* +Add an existing election, useful if you want to set up some parameters or add extra candidates. If null an election object will be created for you. + + +### Return value: + +*(```CondorcetPHP\Condorcet\Election```)* The election object + + +--------------------------------------- + +### Related method(s) + +* [Tools\CondorcetElectionFormat::setDataToAnElection](../Tools\CondorcetElectionFormat%20Class/public%20Tools\CondorcetElectionFormat--setDataToAnElection.md) +* [Tools\DavidHillFormat::setDataToAnElection](../Tools\DavidHillFormat%20Class/public%20Tools\DavidHillFormat--setDataToAnElection.md) diff --git a/include/Condorcet/Documentation/Utils_CondorcetUtil Class/public static Utils_CondorcetUtil--format.md b/include/Condorcet/Documentation/Utils_CondorcetUtil Class/public static Utils_CondorcetUtil--format.md new file mode 100644 index 0000000..ddd9c04 --- /dev/null +++ b/include/Condorcet/Documentation/Utils_CondorcetUtil Class/public static Utils_CondorcetUtil--format.md @@ -0,0 +1,24 @@ +## public static Utils\CondorcetUtil::format + +### Description + +```php +public static Utils\CondorcetUtil::format ( mixed $input [, bool $convertObject = true] ): mixed +``` + +Provide pretty re-formatting, human compliant, of all Condorcet PHP object or result set. +Can be use before a var_dump, or just to get more simple data output. + + +##### **input:** *```mixed```* +Input to convert. + + +##### **convertObject:** *```bool```* +If true. Will convert Candidate objects into string representation of their name. + + +### Return value: + +*(```mixed```)* New formatted data. + diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--__construct.md b/include/Condorcet/Documentation/Vote Class/public Vote--__construct.md new file mode 100644 index 0000000..1c4b36c --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--__construct.md @@ -0,0 +1,43 @@ +## public Vote::__construct + +### Description + +```php +public Vote->__construct ( array|string $ranking [, array|string|null $tags = null , ?float $ownTimestamp = null , ?CondorcetPHP\Condorcet\Election $electionContext = null] ) +``` + +Build a vote object. + + +##### **ranking:** *```array|string```* +Equivalent to Vote::setRanking method. + + +##### **tags:** *```array|string|null```* +Equivalent to Vote::addTags method. + + +##### **ownTimestamp:** *```?float```* +Set your own timestamp metadata on Ranking. + + +##### **electionContext:** *```?CondorcetPHP\Condorcet\Election```* +Try to convert directly your candidates from sting input" to Candidate object of one election. + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\VoteInvalidFormatException``` + +--------------------------------------- + +### Related method(s) + +* [Vote::setRanking](../Vote%20Class/public%20Vote--setRanking.md) +* [Vote::addTags](../Vote%20Class/public%20Vote--addTags.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Add Vote](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote)** diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--addTags.md b/include/Condorcet/Documentation/Vote Class/public Vote--addTags.md new file mode 100644 index 0000000..8904756 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--addTags.md @@ -0,0 +1,36 @@ +## public Vote::addTags + +### Description + +```php +public Vote->addTags ( array|string $tags ): bool +``` + +Add tag(s) on this Vote. + + +##### **tags:** *```array|string```* +Tag(s) are non-numeric alphanumeric string. They can be added by string separated by commas or an array. + + +### Return value: + +*(```bool```)* In case of success, return TRUE + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\VoteInvalidFormatException``` + +--------------------------------------- + +### Related method(s) + +* [Vote::removeTags](../Vote%20Class/public%20Vote--removeTags.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Add Vote](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote)** diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--countLinks.md b/include/Condorcet/Documentation/Vote Class/public Vote--countLinks.md new file mode 100644 index 0000000..31efc06 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--countLinks.md @@ -0,0 +1,25 @@ +## public Vote::countLinks + +### Description + +```php +public Vote->countLinks ( ): int +``` + +Count number of linked election to this object. + + +### Return value: + +*(```int```)* Number of linked elections. + + +--------------------------------------- + +### Related method(s) + +* [Candidate::countLinks](../Candidate%20Class/public%20Candidate--countLinks.md) +* [Vote::getLinks](../Vote%20Class/public%20Vote--getLinks.md) +* [Candidate::getLinks](../Candidate%20Class/public%20Candidate--getLinks.md) +* [Vote::haveLink](../Vote%20Class/public%20Vote--haveLink.md) +* [Candidate::haveLink](../Candidate%20Class/public%20Candidate--haveLink.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--countRankingCandidates.md b/include/Condorcet/Documentation/Vote Class/public Vote--countRankingCandidates.md new file mode 100644 index 0000000..4e53fdd --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--countRankingCandidates.md @@ -0,0 +1,15 @@ +## public Vote::countRankingCandidates + +### Description + +```php +public Vote->countRankingCandidates ( ): int +``` + +Count the number of candidate provide into the active Ranking set. + + +### Return value: + +*(```int```)* Number of Candidate into ranking. + diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getAllCandidates.md b/include/Condorcet/Documentation/Vote Class/public Vote--getAllCandidates.md new file mode 100644 index 0000000..a0c6442 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getAllCandidates.md @@ -0,0 +1,22 @@ +## public Vote::getAllCandidates + +### Description + +```php +public Vote->getAllCandidates ( ): array +``` + +Get all the candidates object set in the last ranking of this Vote. + + +### Return value: + +*(```array```)* Candidates list. + + +--------------------------------------- + +### Related method(s) + +* [Vote::getRanking](../Vote%20Class/public%20Vote--getRanking.md) +* [Vote::countRankingCandidates](../Vote%20Class/public%20Vote--countRankingCandidates.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getContextualRanking.md b/include/Condorcet/Documentation/Vote Class/public Vote--getContextualRanking.md new file mode 100644 index 0000000..4b98522 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getContextualRanking.md @@ -0,0 +1,31 @@ +## public Vote::getContextualRanking + +### Description + +```php +public Vote->getContextualRanking ( CondorcetPHP\Condorcet\Election $election ): array +``` + +Return the vote actual ranking complete for the contexte of the provide election. Election must be linked to the Vote object. + + +##### **election:** *```CondorcetPHP\Condorcet\Election```* +An election already linked to the Vote. + + +### Return value: + +*(```array```)* Contextual full ranking. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\VoteNotLinkedException``` + +--------------------------------------- + +### Related method(s) + +* [Vote::getContextualRankingAsString](../Vote%20Class/public%20Vote--getContextualRankingAsString.md) +* [Vote::getRanking](../Vote%20Class/public%20Vote--getRanking.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getContextualRankingAsString.md b/include/Condorcet/Documentation/Vote Class/public Vote--getContextualRankingAsString.md new file mode 100644 index 0000000..a1ee309 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getContextualRankingAsString.md @@ -0,0 +1,26 @@ +## public Vote::getContextualRankingAsString + +### Description + +```php +public Vote->getContextualRankingAsString ( CondorcetPHP\Condorcet\Election $election ): array +``` + +Return the vote actual ranking complete for the contexte of the provide election. Election must be linked to the Vote object. + + +##### **election:** *```CondorcetPHP\Condorcet\Election```* +An election already linked to the Vote. + + +### Return value: + +*(```array```)* Contextual full ranking, with string instead Candidate object. + + +--------------------------------------- + +### Related method(s) + +* [Vote::getContextualRanking](../Vote%20Class/public%20Vote--getContextualRanking.md) +* [Vote::getRanking](../Vote%20Class/public%20Vote--getRanking.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getCreateTimestamp.md b/include/Condorcet/Documentation/Vote Class/public Vote--getCreateTimestamp.md new file mode 100644 index 0000000..c70f4fd --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getCreateTimestamp.md @@ -0,0 +1,21 @@ +## public Vote::getCreateTimestamp + +### Description + +```php +public Vote->getCreateTimestamp ( ): float +``` + +Get the timestamp corresponding of the creation of this vote. + + +### Return value: + +*(```float```)* Timestamp + + +--------------------------------------- + +### Related method(s) + +* [Candidate::getTimestamp](../Candidate%20Class/public%20Candidate--getTimestamp.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getHashCode.md b/include/Condorcet/Documentation/Vote Class/public Vote--getHashCode.md new file mode 100644 index 0000000..c16e527 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getHashCode.md @@ -0,0 +1,21 @@ +## public Vote::getHashCode + +### Description + +```php +public Vote->getHashCode ( ): string +``` + +Get Object hash (cryptographic) + + +### Return value: + +*(```string```)* SHA hash code. + + +--------------------------------------- + +### Related method(s) + +* [Vote::getWeight](../Vote%20Class/public%20Vote--getWeight.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getHistory.md b/include/Condorcet/Documentation/Vote Class/public Vote--getHistory.md new file mode 100644 index 0000000..8086363 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getHistory.md @@ -0,0 +1,21 @@ +## public Vote::getHistory + +### Description + +```php +public Vote->getHistory ( ): array +``` + +Return an history of each vote change, with timestamp. + + +### Return value: + +*(```array```)* An explicit multi-dimenssional array. + + +--------------------------------------- + +### Related method(s) + +* [Vote::getCreateTimestamp](../Vote%20Class/public%20Vote--getCreateTimestamp.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getLinks.md b/include/Condorcet/Documentation/Vote Class/public Vote--getLinks.md new file mode 100644 index 0000000..ae0f4c7 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getLinks.md @@ -0,0 +1,25 @@ +## public Vote::getLinks + +### Description + +```php +public Vote->getLinks ( ): WeakMap +``` + +Get elections object linked to this Vote or Candidate object. + + +### Return value: + +*(```WeakMap```)* Populated by each elections Condorcet object. + + +--------------------------------------- + +### Related method(s) + +* [Vote::countLinks](../Vote%20Class/public%20Vote--countLinks.md) +* [Candidate::countLinks](../Candidate%20Class/public%20Candidate--countLinks.md) +* [Candidate::getLinks](../Candidate%20Class/public%20Candidate--getLinks.md) +* [Vote::haveLink](../Vote%20Class/public%20Vote--haveLink.md) +* [Candidate::haveLink](../Candidate%20Class/public%20Candidate--haveLink.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getObjectVersion.md b/include/Condorcet/Documentation/Vote Class/public Vote--getObjectVersion.md new file mode 100644 index 0000000..7b47eab --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getObjectVersion.md @@ -0,0 +1,25 @@ +## public Vote::getObjectVersion + +### Description + +```php +public Vote->getObjectVersion ( [bool $major = false] ): string +``` + +Get the Condorcet PHP version who built this Election object. Usefull pour serializing Election. + + +##### **major:** *```bool```* +true will return : '2.0' and false will return : '2.0.0'. + + +### Return value: + +*(```string```)* Condorcet PHP version. + + +--------------------------------------- + +### Related method(s) + +* [static Condorcet::getVersion](../Condorcet%20Class/public%20static%20Condorcet--getVersion.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getRanking.md b/include/Condorcet/Documentation/Vote Class/public Vote--getRanking.md new file mode 100644 index 0000000..132b1bc --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getRanking.md @@ -0,0 +1,25 @@ +## public Vote::getRanking + +### Description + +```php +public Vote->getRanking ( [bool $sortCandidatesInRank = true] ): array +``` + +Get the actual Ranking of this Vote. + + +##### **sortCandidatesInRank:** *```bool```* +Sort Candidate in a Rank by name. Useful for performant internal calls from methods. + + +### Return value: + +*(```array```)* Multidimenssionnal array populated by Candidate object. + + +--------------------------------------- + +### Related method(s) + +* [Vote::setRanking](../Vote%20Class/public%20Vote--setRanking.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getSimpleRanking.md b/include/Condorcet/Documentation/Vote Class/public Vote--getSimpleRanking.md new file mode 100644 index 0000000..d03061b --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getSimpleRanking.md @@ -0,0 +1,29 @@ +## public Vote::getSimpleRanking + +### Description + +```php +public Vote->getSimpleRanking ( [?CondorcetPHP\Condorcet\Election $context = null , bool $displayWeight = true] ): string +``` + +Get the current ranking as a string format. Optionally with an election context, see Election::getContextualRanking() + + +##### **context:** *```?CondorcetPHP\Condorcet\Election```* +An election already linked to the Vote. + + +##### **displayWeight:** *```bool```* +Include or not the weight symbol and value. + + +### Return value: + +*(```string```)* String like 'A>D=C>B' + + +--------------------------------------- + +### Related method(s) + +* [Vote::getRanking](../Vote%20Class/public%20Vote--getRanking.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getTags.md b/include/Condorcet/Documentation/Vote Class/public Vote--getTags.md new file mode 100644 index 0000000..5bbef87 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getTags.md @@ -0,0 +1,23 @@ +## public Vote::getTags + +### Description + +```php +public Vote->getTags ( ): array +``` + +Get the registered tags for this Vote. + + +### Return value: + +*(```array```)* List of registered tag. + + +--------------------------------------- + +### Related method(s) + +* [Vote::getTagsAsString](../Vote%20Class/public%20Vote--getTagsAsString.md) +* [Vote::addTags](../Vote%20Class/public%20Vote--addTags.md) +* [Vote::removeTags](../Vote%20Class/public%20Vote--removeTags.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getTagsAsString.md b/include/Condorcet/Documentation/Vote Class/public Vote--getTagsAsString.md new file mode 100644 index 0000000..83fc320 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getTagsAsString.md @@ -0,0 +1,23 @@ +## public Vote::getTagsAsString + +### Description + +```php +public Vote->getTagsAsString ( ): string +``` + +Get the registered tags for this Vote. + + +### Return value: + +*(```string```)* List of registered tag as string separated by commas. + + +--------------------------------------- + +### Related method(s) + +* [Vote::getTags](../Vote%20Class/public%20Vote--getTags.md) +* [Vote::addTags](../Vote%20Class/public%20Vote--addTags.md) +* [Vote::removeTags](../Vote%20Class/public%20Vote--removeTags.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getTimestamp.md b/include/Condorcet/Documentation/Vote Class/public Vote--getTimestamp.md new file mode 100644 index 0000000..75f2d26 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getTimestamp.md @@ -0,0 +1,21 @@ +## public Vote::getTimestamp + +### Description + +```php +public Vote->getTimestamp ( ): float +``` + +Get the timestamp corresponding of the last vote change. + + +### Return value: + +*(```float```)* Timestamp + + +--------------------------------------- + +### Related method(s) + +* [Vote::getCreateTimestamp](../Vote%20Class/public%20Vote--getCreateTimestamp.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--getWeight.md b/include/Condorcet/Documentation/Vote Class/public Vote--getWeight.md new file mode 100644 index 0000000..336c810 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--getWeight.md @@ -0,0 +1,25 @@ +## public Vote::getWeight + +### Description + +```php +public Vote->getWeight ( [?CondorcetPHP\Condorcet\Election $context = null] ): int +``` + +Get the vote weight. The vote weight capacity must be active at the election level for producing effect on the result. + + +##### **context:** *```?CondorcetPHP\Condorcet\Election```* +In the context of wich election? (optional). + + +### Return value: + +*(```int```)* Weight. Default weight is 1. + + +--------------------------------------- + +### Related method(s) + +* [Vote::setWeight](../Vote%20Class/public%20Vote--setWeight.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--haveLink.md b/include/Condorcet/Documentation/Vote Class/public Vote--haveLink.md new file mode 100644 index 0000000..04cd837 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--haveLink.md @@ -0,0 +1,29 @@ +## public Vote::haveLink + +### Description + +```php +public Vote->haveLink ( CondorcetPHP\Condorcet\Election $election ): bool +``` + +Check if this election is linked with this Candidate/Vote object. + + +##### **election:** *```CondorcetPHP\Condorcet\Election```* +Condorcet election to check. + + +### Return value: + +*(```bool```)* True or False. + + +--------------------------------------- + +### Related method(s) + +* [Vote::countLinks](../Vote%20Class/public%20Vote--countLinks.md) +* [Candidate::countLinks](../Candidate%20Class/public%20Candidate--countLinks.md) +* [Vote::getLinks](../Vote%20Class/public%20Vote--getLinks.md) +* [Candidate::getLinks](../Candidate%20Class/public%20Candidate--getLinks.md) +* [Candidate::haveLink](../Candidate%20Class/public%20Candidate--haveLink.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--removeAllTags.md b/include/Condorcet/Documentation/Vote Class/public Vote--removeAllTags.md new file mode 100644 index 0000000..8ac7012 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--removeAllTags.md @@ -0,0 +1,22 @@ +## public Vote::removeAllTags + +### Description + +```php +public Vote->removeAllTags ( ): bool +``` + +Remove all registered tag(s) on this Vote. + + +### Return value: + +*(```bool```)* Return True. + + +--------------------------------------- + +### Related method(s) + +* [Vote::addTags](../Vote%20Class/public%20Vote--addTags.md) +* [Vote::removeTags](../Vote%20Class/public%20Vote--removeTags.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--removeCandidate.md b/include/Condorcet/Documentation/Vote Class/public Vote--removeCandidate.md new file mode 100644 index 0000000..e85e68f --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--removeCandidate.md @@ -0,0 +1,30 @@ +## public Vote::removeCandidate + +### Description + +```php +public Vote->removeCandidate ( CondorcetPHP\Condorcet\Candidate|string $candidate ): bool +``` + +Remove candidate from ranking. Set a new ranking and archive the old ranking. + + +##### **candidate:** *```CondorcetPHP\Condorcet\Candidate|string```* +Candidate object or string. + + +### Return value: + +*(```bool```)* True on success. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\CandidateDoesNotExistException``` + +--------------------------------------- + +### Related method(s) + +* [Vote::setRanking](../Vote%20Class/public%20Vote--setRanking.md) diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--removeTags.md b/include/Condorcet/Documentation/Vote Class/public Vote--removeTags.md new file mode 100644 index 0000000..6e07ed1 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--removeTags.md @@ -0,0 +1,31 @@ +## public Vote::removeTags + +### Description + +```php +public Vote->removeTags ( array|string $tags ): array +``` + +Remove registered tag(s) on this Vote. + + +##### **tags:** *```array|string```* +They can be added by string separated by commas or an array. + + +### Return value: + +*(```array```)* List of deleted tags. + + +--------------------------------------- + +### Related method(s) + +* [Vote::addTags](../Vote%20Class/public%20Vote--addTags.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Add Vote](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote)** diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--setRanking.md b/include/Condorcet/Documentation/Vote Class/public Vote--setRanking.md new file mode 100644 index 0000000..3af0c80 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--setRanking.md @@ -0,0 +1,44 @@ +## public Vote::setRanking + +### Description + +```php +public Vote->setRanking ( array|string $ranking [, ?float $ownTimestamp = null] ): bool +``` + +Set a new ranking for this vote. + +Note that if your vote is already linked to one ore more elections, your ranking must be compliant with all of them, else an exception is throw. For do this, you need to use only valid Candidate object, you can't register a new ranking from string if your vote is already linked to an election. + + +##### **ranking:** *```array|string```* +A Ranking. Have a look at the Wiki https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote to learn the available ranking formats. + + +##### **ownTimestamp:** *```?float```* +Set your own timestamp metadata on Ranking. Your timestamp must be > than last registered timestamp. Else, an exception will be throw. + + +### Return value: + +*(```bool```)* In case of success, return TRUE + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\VoteInvalidFormatException``` + +--------------------------------------- + +### Related method(s) + +* [Vote::getRanking](../Vote%20Class/public%20Vote--getRanking.md) +* [Vote::getHistory](../Vote%20Class/public%20Vote--getHistory.md) +* [Vote::__construct](../Vote%20Class/public%20Vote--__construct.md) + +--------------------------------------- + +### Examples and explanation + +* **[Manual - Add a vote](https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote)** diff --git a/include/Condorcet/Documentation/Vote Class/public Vote--setWeight.md b/include/Condorcet/Documentation/Vote Class/public Vote--setWeight.md new file mode 100644 index 0000000..18fda38 --- /dev/null +++ b/include/Condorcet/Documentation/Vote Class/public Vote--setWeight.md @@ -0,0 +1,30 @@ +## public Vote::setWeight + +### Description + +```php +public Vote->setWeight ( int $newWeight ): int +``` + +Set a vote weight. The vote weight capacity must be active at the election level for producing effect on the result. + + +##### **newWeight:** *```int```* +The new vote weight. + + +### Return value: + +*(```int```)* New weight. + + + +### Throws: + +* ```CondorcetPHP\Condorcet\Throwable\VoteInvalidFormatException``` + +--------------------------------------- + +### Related method(s) + +* [Vote::getWeight](../Vote%20Class/public%20Vote--getWeight.md) diff --git a/include/Condorcet/Examples/1. Overview.php b/include/Condorcet/Examples/1. Overview.php new file mode 100644 index 0000000..40bf724 --- /dev/null +++ b/include/Condorcet/Examples/1. Overview.php @@ -0,0 +1,214 @@ +addCandidate('Debussy'); + $election1->addCandidate('Caplet'); + $election1->addCandidate('A'); + $myLutoCandidate = $election1->addCandidate('Lutoslawski'); // Return the CondorcetPHP\Candidate object. + + // Automatic name + $myAutomaticCandidate = $election1->addCandidate(); // Return an automatic CondorcetPHP\Candidate + $myAutomaticCandidate->getName(); // return 'B'. Because we have already register the A candidate above. + + // An objet + $myMessiaenCandidate = new Candidate('Olivier Messiaen'); + + $election1->addCandidate($myMessiaenCandidate); + $election1->addCandidate(new Candidate('Ligeti')); + $election1->addCandidate(new Candidate('Koechlin')); + + + # -B- Change your mind ? + + $election1->removeCandidates('A'); + $election1->removeCandidates($myAutomaticCandidate); + + // Lutoslawski change his name + $myLutoCandidate->setName('Wiltod Lutoslawski'); # Done ! + + # What was his old names? + $myLutoCandidate->getHistory(); // return the full history with timestamp of this Candidate naming + + + # -C- Check your candidate list, if you forget it + $election1->getCandidatesList(); // Return an array pupulate by each Candidate objet + $election1->getCandidatesList(true); // Return an array pupulate by each Candidate name as String. + + // OK, I need my Debussy (want his candidate object) + $myDebussyCandidate = $election1->getCandidateObjectFromName('Debussy'); + + +// IV - Votes + + # -A- Add Votes + + $myVote1 = $election1->addVote([ + 1 => 'Debussy', + 2 => ['Caplet', 'Olivier Messiaen'], + 3 => 'Ligeti', + 4 => ['Wiltod Lutoslawski', 'Koechlin'], + ]); // Return the new Vote object + + $myVote2 = new Vote([ + 1 => 'Debussy', + 2 => 'Caplet', + 3 => 'Olivier Messiaen', + 4 => 'Koechlin', + 5 => 'Wiltod Lutoslawski', + 6 => 'Ligeti', + ]); + + $election1->addVote($myVote2); + + $myVote3 = $election1->addVote('Debussy > Olivier Messiaen = Ligeti > Caplet'); // Last rank rank will be automatically deducted. A vote can not be expressed on a limited number of candidates. Non-expressed candidates are presumed to last Exaequo + + // Off course, you can vote by candidate object. Which is recommended. Or mix the two methods. + $myVote4 = $election1->addVote([ + $election1->getCandidateObjectFromName('Ligeti'), + $myLutoCandidate, + [$myMessiaenCandidate, 'Koechlin'], + + ]); + + // Finishing with another really nice example. + $myVote5 = new Vote([ + 1 => $election1->getCandidateObjectFromName('Debussy'), + 2 => $election1->getCandidateObjectFromName('Olivier Messiaen'), + 3 => [$election1->getCandidateObjectFromName('Wiltod Lutoslawski'), $election1->getCandidateObjectFromName('Ligeti')], + 4 => $election1->getCandidateObjectFromName('Koechlin'), + 5 => $election1->getCandidateObjectFromName('Caplet'), + ]); + + $myVote5->addTags('jusGreatVote'); + + $election1->addVote($myVote5); + + // Please note that : + $election1->getCandidateObjectFromName('Olivier Messiaen') === $myMessiaenCandidate; // Return TRUE + + + // Add some nice tags to my Vote 1 & 2 & 3 (You can do this before or after add register into to the Election) + + $myVote1->addTags(['strangeVote', 'greatFrenchVote']); // By Array + $myVote2->addTags('greatFrenchVote,chauvinismVote'); // By String + $myVote3->addTags($myVote1->getTags()); // Copy & Past + + + // Parsing Vote + $election1->parseVotes(" + Ligeti > Wiltod Lutoslawski > Olivier Messiaen = Debussy > Koechlin # A comment. A a line break for the next vote. + greatFrenchVote,chauvinismVote || Olivier Messiaen > Debussy = Caplet > Ligeti # Tags at first, vote at second, separated by '||' + strangeVote || Caplet > Koechlin * 8 # This vote and his tag will be register 8 times. + "); + + + // Adding some random to this election + $VoteModel = $myVote2->getRanking(); + + for ($i = 0; $i < 95; $i++) { + shuffle($VoteModel); + $election1->addVote($VoteModel); + } + + // How Many Vote could I Have now ? + $election1->countVotes(); // Return 110 (int) + + + # -B- Manage Votes + + # 1- Get vote list + // Get the vote list + $election1->getVotesList(); // Returns an array of all votes as object. + + // How many Vote with tag "strangeVote" ? + $election1->countVotes('strangeVote'); // Return 10 (int) + // Or without + $election1->countVotes('strangeVote', false); // Return 100 (int) + // Or with this tag OR this tag + $election1->countVotes(['greatFrenchVote', 'chauvinismVote']); // Return 4 (int) + // Or without this tag AND this tag + $election1->countVotes(['greatFrenchVote', 'chauvinismVote'], false); // Return 4 (int) + + // Return this 10 votes ! + $election1->getVotesList('strangeVote'); + // And the others 100 votes without this tags + $election1->getVotesList('strangeVote', false); + // Or with this tag OR this tag + $election1->getVotesList(['greatFrenchVote', 'chauvinismVote']); // Return 4 (int) + // Or without this tag AND this tag + $election1->getVotesList(['greatFrenchVote', 'chauvinismVote'], false); // Return 4 (int) + + + # 2- Vote objet + $myVote3->getRanking(); // This vote specifies four candidates. Although the election comprises 6. This method return the original input. + $myVote3->getContextualRanking($election1); // Return the full ranking in the context of election 1 (with 6 candidates). It is one that is taken into account when calculating the results of the election 1. + + + // Change the vote + $myVote1->setRanking([ + 1 => 'Caplet', + 2 => 'Debussy', + 3=> 'Koechlin', + ]); + + + // Check the vote history + $myVote1History = $myVote1->getHistory(); + + + # 3- Delete Votes + + // Delete a specific vote object + $election1->removeVote($myVote3); + + // Delete all vote with tag "strangeVote" or "frenchies" + $election1->removeVotesByTags(['strangeVote', 'chauvinismVote']); + + // Count vote + $election1->countVotes(); // Return 98 + + +// V - Get Result + + // Natural Condorcet Winner + $election1->getWinner(); // Return NULL if there is not, else return the winner candidate object + $election1->getWinner('Schulze'); // Same thing, but try to resolve winner by Schulze method that extends Condorcet method. Can return an array of winners if there are multiple. + + // Natural Condorcet Loser + $election1->getLoser(); // Return NULL if there is not, else return the winner candidate object + $election1->getLoser('Schulze'); // Same thing, but try to resolve loser by Schulze method that extends Condorcet method. Can return an array of winners if there are multiple. + + // Advanced Method - + $election1->getResult(); // Result set for defaut method (Should be Schulze Winning). That return a Condorcet/Result object. + $election1->getResult('Copeland'); // Do it with the Copeland method + + /* Please note that a Result object is fixed and independent. It does not change automatically when you change the election. You then have requested the production of a new object result. */ + + // Get an easy game outcome to read and understand (Table populated by string) + $election1->getResult('Schulze')->getResultAsArray(true); + + +print $firstPart ?? 'Success! +Process in: '. round(microtime(true) - $start_time, 3) . 's'; diff --git a/include/Condorcet/Examples/2. AdvancedObjectManagement.php b/include/Condorcet/Examples/2. AdvancedObjectManagement.php new file mode 100644 index 0000000..14e1c01 --- /dev/null +++ b/include/Condorcet/Examples/2. AdvancedObjectManagement.php @@ -0,0 +1,89 @@ +addCandidate(); + } + + + # Same candidate in multiple elections + + // Add two participating candidates from $election1 + $election2->addCandidate($election1->getCandidateObjectFromName('Debussy')); + $election2->addCandidate($myLutoCandidate); + + // And, I can change again theirs name. The new name is now applied in the two elections and their votes. If namesake in another election, an exception is throw. + $myLutoCandidate->setName('W.Lutoslawski'); + + // Have a look on $myLutoCandidate history + $myLutoCandidate->getHistory(); + + // Have a look to a vote from $election1. The candidate name have changed. + $myVote4->getContextualRanking($election1, true); + + // In what elections, this candidates have a part ? + $myLutoCandidate->getLinks(); // Get his the two Election objects + $myLutoCandidate->countLinks(); // Or just count it. return (int) 2. + + + # The same vote applied to multiple elections. + + $myNewVote = new Vote([ + 1 => $election1->getCandidateObjectFromName('Debussy'), + 2 => $election2->getCandidateObjectFromName('A'), + 3 => $election1->getCandidateObjectFromName('Olivier Messiaen'), + 4 => $election2->getCandidateObjectFromName('B'), + 5 => $election1->getCandidateObjectFromName('Koechlin'), + 6 => $election1->getCandidateObjectFromName('W.Lutoslawski'), + 7 => new Candidate('Another candidate'), // This one does not takes part in any of two elections. + 8 => $election1->getCandidateObjectFromName('Caplet'), + 9 => $election2->getCandidateObjectFromName('C'), + ]); + + // Add it on election 1 and 2 + $election1->addVote($myNewVote); + $election2->addVote($myNewVote); + + // Check ranking + $myNewVote->getRanking(); // Here you get the original 9 ranks. + // Check ranking + $myNewVote->getContextualRanking($election1); // Here you get the vote applicable to $election 1 + $myNewVote->getContextualRanking($election2); // Here you get the vote applicable to $election 2 + + // In what election, this candidates have a part ? + $myNewVote->getLinks(); // Get Condorcet objects + $myNewVote->countLinks(); // Or just count it + + // Now we can change vote ranking. result from all election will be affected. + $myNewVote->setRanking([ + 1 => $election2->getCandidateObjectFromName('B'), + 2 => $election1->getCandidateObjectFromName('Koechlin'), + 3 => $election1->getCandidateObjectFromName('W.Lutoslawski'), + ]); + + # Get Ranking history + $myNewVote->getHistory(); + + +print 'Success! +Process in: '. round(microtime(true) - $start_time, 3) . 's'; diff --git a/include/Condorcet/Examples/Examples-with-html/A.Global_Example.php b/include/Condorcet/Examples/Examples-with-html/A.Global_Example.php new file mode 100644 index 0000000..683e2c4 --- /dev/null +++ b/include/Condorcet/Examples/Examples-with-html/A.Global_Example.php @@ -0,0 +1,182 @@ + + + + + <?php echo TEST_NAME; ?> + + + + + +
+ Condorcet Class +
+ +

+ + Condorcet Class version :
+ + + Number of Candidates : + countCandidates(); ?> + | + Number of votes : + countVotes(); ?> + + +

Candidates list :

+ + + + +

Registered votes details :

+getVotesList() as $vote) { + echo '
'; + + echo ''.implode(' / ', $vote->getTags()).'
'; + + echo '
    '; + + foreach ($vote as $rank => $value) { + if ($rank === 'tag') { + continue; + } ?> + +
  1. + +
'; + } +?> + +
+ +

Winner by natural Condorcet :

+ + + getWinner() !== null) { + echo $election->getWinner(); + } else { + echo 'The votes of this group do not allow natural Condorcet winner because of Condorcet paradox.'; + } + ?> +
+ computed in getLastTimer(), 5); ?> second(s).
+ +

Loser by natural Condorcet :

+ + + getLoser() !== null) { + echo $election->getLoser(); + } else { + echo 'The votes of this group do not allow natural Condorcet loser because of Condorcet paradox.'; + } + ?> +
+ computed in getLastTimer(), 5); ?> second(s).
+ + +


+ + + +

Ranking by :

+ + getResult($method); + $lastTimer = $election->getLastTimer(); + + if ($method === 'Kemeny–Young' && !empty($result->getWarning(\CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung::CONFLICT_WARNING_CODE))) { + $kemeny_conflicts = explode(';', $result->getWarning(\CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung::CONFLICT_WARNING_CODE)[0]['msg']); + + echo 'Arbitrary results: Kemeny-Young has '.$kemeny_conflicts[0].' possible solutions at score '.$kemeny_conflicts[1].''; + } + ?> + +
+		
+		
+ + computed in second(s). + + +



+Total computed in getGlobalTimer(), 5); ?> second(s). +
+getTimerManager()->getHistory()); ?> +


+ +

Computing statistics :

+ +

Pairwise :

+ +
+	getPairwise())); ?>
+	
+ + +

Stats for :

+ +
+		getResult($method)->getStats())); ?>
+		
+ + + +


+ +

Debug Data :

+ +

Defaut method (not used explicitly before) :

+ +
+
+ 
+ + + + + \ No newline at end of file diff --git a/include/Condorcet/Examples/Examples-with-html/B.Ranking_Manipulation.php b/include/Condorcet/Examples/Examples-with-html/B.Ranking_Manipulation.php new file mode 100644 index 0000000..e7db459 --- /dev/null +++ b/include/Condorcet/Examples/Examples-with-html/B.Ranking_Manipulation.php @@ -0,0 +1,254 @@ + + + + + <?php echo TEST_NAME; ?> + + + + + +
+ Condorcet Class +
+ +

+ + Condorcet Class version :
+ + + Number of Candidates : + countCandidates(); ?> + | + Number of votes : + countVotes(); ?> + + +

Candidates list :

+ + + + +

Registered votes details :

+getVotesList() as $vote) { + echo '
'; + + echo ''.implode(' / ', $vote->getTags()).'
'; + + echo '
    '; + + foreach ($vote as $rank => $value) { + if ($rank === 'tag') { + continue; + } ?> + +
  1. + +
'; + } +?> + +

+ +

Get pairwise :

+ +
+	getPairwise())); ?>
+	 
+
+ computed in getLastTimer(), 5); ?> second(s). + +


+ +

Winner by natural Condorcet :

+ + + getWinner() !== null) { + echo $election->getWinner(); + } else { + echo 'The votes of this group do not allow natural Condorcet winner because of Condorcet paradox.'; + } + ?> + + +

Loser by natural Condorcet :

+ + + getLoser() !== null) { + echo $election->getLoser(); + } else { + echo 'The votes of this group do not allow natural Condorcet loser because of Condorcet paradox.'; + } + ?> + + +


+ +

Some pratices about default method :

+ +

Use default method :

+ + Defaut:
+ +
+	getResult())); ?>
+	 
+ +

Change it to MiniMax_Margin :

+ + + Defaut:
+ +
+	getResult())); ?>
+	 
+ + +


+ +

Vote manipulation :

+ +

Display votes with tag "custom_tag_One"

+getVotesList('custom_tag_One', true) as $vote) { + echo '
'; + + echo ''.implode(' / ', $vote->getTags()).'
'; + + echo '
    '; + + foreach ($vote as $rank => $value) { + if ($rank === 'tag') { + continue; + } ?> + +
  1. + +
'; + } +?> +
+ +

Or without with tag "custom_tag_Two"

+getVotesList('custom_tag_Two', false) as $vote) { + echo '
'; + + echo ''.implode(' / ', $vote->getTags()).'
'; + + echo '
    '; + + foreach ($vote as $rank => $value) { + if ($rank === 'tag') { + continue; + } ?> + +
  1. + +
'; + } +?> +
+ +

Get a ranking without "custom_tag_One" & "custom_tag_Two" tags and display Kemeny-Young result but don't delete it

+ +
+	 ['custom_tag_One', 'custom_tag_Two'],
+        'withTag' => false,
+    ];
+
+    var_dump(CondorcetUtil::format($election->getResult('KemenyYoung', $options))); ?>
+	 
+
+ +

Delete vote with "custom_tag_One" & "custom_tag_Two" tags and display Kemeny-Young result

+ + removeVotesByTags(['custom_tag_One', 'custom_tag_Two']); + ?> + + +
+	getResult('KemenyYoung'))); ?>
+	 
+ + +

Check the new vote list

+getVotesList() as $vote) { + echo '
'; + + echo ''.implode(' / ', $vote->getTags()).'
'; + + echo '
    '; + + foreach ($vote as $rank => $value) { + if ($rank === 'tag') { + continue; + } ?> + +
  1. + +
'; + } +?> +
+ + + +


+ + + + + \ No newline at end of file diff --git a/include/Condorcet/Examples/Examples-with-html/vote_data/BasicVoteConf.php b/include/Condorcet/Examples/Examples-with-html/vote_data/BasicVoteConf.php new file mode 100644 index 0000000..7e73c1d --- /dev/null +++ b/include/Condorcet/Examples/Examples-with-html/vote_data/BasicVoteConf.php @@ -0,0 +1,54 @@ +addCandidate('Memphis'); +$election->addCandidate('Nashville'); +$election->addCandidate('Chatta'); +$election->addCandidate('Knoxville'); + + +// Votes +$vote[] = 'Memphis'; +$vote[] = 'Nashville'; +$vote[] = 'Chatta'; +$vote[] = 'Knoxville'; + +for ($i = 1; $i <= 42; $i++) { + $election->addVote($vote, 'custom_tag_Two'); +} +$vote = []; + +$vote[] = 'Nashville'; +$vote[] = 'Chatta'; +$vote[] = 'Knoxville'; +$vote[] = 'Memphis'; + +for ($i = 1; $i <= 26; $i++) { + $election->addVote($vote, 'custom_tag_Two'); +} +$vote = []; + +$vote[] = 'Chatta'; +$vote[] = 'Knoxville'; +$vote[] = 'Nashville'; +$vote[] = 'Memphis'; + +for ($i = 1; $i <= 12; $i++) { + $election->addVote($vote); +} +for ($i = 1; $i <= 3; $i++) { + $election->addVote($vote, 'custom_tag_One'); +} +$vote = []; + +$vote[] = 'Knoxville'; +$vote[] = 'Chatta'; +$vote[] = 'Nashville'; +$vote[] = 'Memphis'; + +for ($i = 1; $i <= 17; $i++) { + $election->addVote($vote, 'custom_tag_Two'); +} +$vote = []; diff --git a/include/Condorcet/Examples/Examples-with-html/vote_data/ComplexeVoteConf.php b/include/Condorcet/Examples/Examples-with-html/vote_data/ComplexeVoteConf.php new file mode 100644 index 0000000..b9ae143 --- /dev/null +++ b/include/Condorcet/Examples/Examples-with-html/vote_data/ComplexeVoteConf.php @@ -0,0 +1,91 @@ +addCandidate('A'); +$election->addCandidate('C'); +$election->addCandidate('B'); +$election->addCandidate('E'); +$election->addCandidate('D'); + + +// Votes +$vote = []; +$vote[1] = 'A'; +$vote[2] = 'C'; +$vote[3] = 'B'; +$vote[4] = 'E'; +$vote[5] = 'D'; + +for ($i = 1; $i <= 5; $i++) { + $election->addVote($vote, 'coucou'); +} + +$vote = 'A>D>E>C>B'; + +for ($i = 1; $i <= 5; $i++) { + $election->addVote($vote); +} + + +$vote = 'B>E>D>A'; // It is not mandatory to indicate the role or last. It will be automatically deducted from the previous. + +for ($i = 1; $i <= 8; $i++) { + $election->addVote($vote); +} + +$vote = []; +$vote[1] = 'C'; +$vote[2] = 'A'; +$vote[3] = 'B'; +$vote[4] = 'E'; +// It is not mandatory to indicate the role or last. It will be automatically deducted from the previous. + +for ($i = 1; $i <= 3; $i++) { + $election->addVote($vote); +} + +$vote = []; +$vote[1] = 'C'; +$vote[2] = 'A'; +$vote[3] = 'E'; +$vote[4] = 'B'; +$vote[5] = 'D'; + +for ($i = 1; $i <= 7; $i++) { + $election->addVote($vote); +} + +$vote = []; +$vote[1] = 'C'; +$vote[2] = 'B'; +$vote[3] = 'A'; +$vote[4] = 'D'; +$vote[5] = 'E'; + +for ($i = 1; $i <= 2; $i++) { + $election->addVote($vote); +} + +$vote = []; +$vote[1] = 'D'; +$vote[2] = 'C'; +$vote[3] = 'E'; +$vote[4] = 'B'; +$vote[5] = 'A'; + +for ($i = 1; $i <= 7; $i++) { + $election->addVote($vote); +} + +$vote = []; +$vote[1] = 'E'; +$vote[2] = 'B'; +$vote[3] = 'A'; +$vote[4] = 'D'; +$vote[5] = 'C'; + +for ($i = 1; $i <= 8; $i++) { + $election->addVote($vote); +} diff --git a/include/Condorcet/Examples/Specifics_Examples/use_large_election_external_database_drivers.php b/include/Condorcet/Examples/Specifics_Examples/use_large_election_external_database_drivers.php new file mode 100644 index 0000000..ae9f161 --- /dev/null +++ b/include/Condorcet/Examples/Specifics_Examples/use_large_election_external_database_drivers.php @@ -0,0 +1,72 @@ +addCandidate(new Candidate('A')); + $myElection->addCandidate(new Candidate('B')); + $myElection->addCandidate(new Candidate('C')); + $myElection->addCandidate(new Candidate('D')); + $myElection->addCandidate(new Candidate('E')); + $myElection->addCandidate(new Candidate('F')); + + // II - Setup external drivers + + /* We will use PDO SQLITE, but can be MySQL or else */ + + if (file_exists(__DIR__.'/bdd.sqlite')) { + unlink(__DIR__.'/bdd.sqlite'); + } + + $pdo_object = new \PDO('sqlite:'.__DIR__.'/bdd.sqlite'); + $database_map = ['tableName' => 'Entities', 'primaryColumnName' => 'id', 'dataColumnName' => 'data']; + + $driver = new PdoHandlerDriver(bdd: $pdo_object, tryCreateTable: true, struct: $database_map); // true = Try to create table + + $myElection->setExternalDataHandler($driver); + +// III - Add hundred of thousands votes + + set_time_limit(60 * 5); + + $howMany = 100000; + + $voteModel = $myElection->getCandidatesList(); + + for ($i = 0; $i < $howMany; $i++) { + shuffle($voteModel); + $myElection->addVote($voteModel); + } + + +// IV - Get somes results + + $myElection->getWinner(); + + $myElection->getResult('Schulze'); + + +print 'Success! +Process in: '. round(microtime(true) - $start_time, 2) . "s\n"; + +echo ' Peak of memory allocated : '.round(memory_get_peak_usage()/1024** ($i=floor(log(memory_get_peak_usage(), 1024))), 2).' '.['b', 'kb', 'mb', 'gb', 'tb', 'pb'][$i]."\n\n"; + + +// Optionally. You can close external driver and and retrieve data into classical internal RAM memory, if there is enough space... +# $myElection->closeHandler(); diff --git a/include/Condorcet/ISSUE_TEMPLATE.md b/include/Condorcet/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..bc8dc34 --- /dev/null +++ b/include/Condorcet/ISSUE_TEMPLATE.md @@ -0,0 +1,15 @@ +| Q | A +| --------------------| --------------- +| Type | Bug / Support / Suggestion / Other +| Condorcet version | x.y.z +| PHP version | x.y.z +| Installation Method | Composer / __CondorcetAutoload.php / Custom + + diff --git a/include/Condorcet/LICENSE.txt b/include/Condorcet/LICENSE.txt new file mode 100644 index 0000000..bb14a34 --- /dev/null +++ b/include/Condorcet/LICENSE.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014-2020 Julien Boudry (France, born on 1988-10-22) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/include/Condorcet/README.md b/include/Condorcet/README.md new file mode 100644 index 0000000..cbdbc07 --- /dev/null +++ b/include/Condorcet/README.md @@ -0,0 +1,218 @@ +

+ Condorcet +

+ +[![Packagist](https://d3g33cz5i5omk9.cloudfront.net/packagist/v/julien-boudry/condorcet.svg?style=for-the-badge)](https://packagist.org/packages/julien-boudry/condorcet) +[![License](https://d3g33cz5i5omk9.cloudfront.net/github/license/mashape/apistatus.svg?style=for-the-badge)](LICENSE.txt) +[![GitHub contributors](https://d3g33cz5i5omk9.cloudfront.net/github/contributors/julien-boudry/Condorcet.svg?style=for-the-badge)](https://github.com/julien-boudry/Condorcet/graphs/contributors) +[![Packagist Download](https://d3g33cz5i5omk9.cloudfront.net/packagist/dt/julien-boudry/condorcet.svg?style=for-the-badge)](https://packagist.org/packages/julien-boudry/condorcet) +![GitHub code size in bytes](https://d3g33cz5i5omk9.cloudfront.net/github/languages/code-size/julien-boudry/Condorcet.svg?style=for-the-badge) + +[![Codacy Badge](https://d3g33cz5i5omk9.cloudfront.net/codacy/grade/f34e354703514ab68248a0c995a4913a?style=for-the-badge)](https://app.codacy.com/gh/julien-boudry/Condorcet/dashboard?utm_source=github.com&utm_medium=referral&utm_content=julien-boudry/Condorcet&utm_campaign=Badge_Grade) +[![Codacy Badge](https://d3g33cz5i5omk9.cloudfront.net/codacy/coverage/f34e354703514ab68248a0c995a4913a?style=for-the-badge)](https://app.codacy.com/gh/julien-boudry/Condorcet/dashboard?utm_source=github.com&utm_medium=referral&) +[![Build Status](https://d3g33cz5i5omk9.cloudfront.net/github/workflow/status/julien-boudry/Condorcet/Execute%20All%20Tests?style=for-the-badge&label=Tests)](https://github.com/julien-boudry/Condorcet/actions) +[![Docker Hub](https://d3g33cz5i5omk9.cloudfront.net/docker/cloud/automated/julienboudry/condorcet?style=for-the-badge&logo=Docker%20Hub%20Condorcet)](https://hub.docker.com/r/julienboudry/condorcet) + +> Main Author: [Julien Boudry](https://www.linkedin.com/in/julienboudry/) +> License: [MIT](LICENSE.txt) _- Please say hello if you like or use this code!_ +> Contribute: [Contribute File](CONTRIBUTING.md) +> Donation: **₿ [bc1qesj74vczsetqjkfcwqymyns9hl6k220dhl0sr9](https://blockchair.com/bitcoin/address/bc1qesj74vczsetqjkfcwqymyns9hl6k220dhl0sr9)** or **[Github Sponsor Page](https://github.com/sponsors/julien-boudry)** +> _You can also offer me a bottle of good wine._ + + +Condorcet PHP +=========================== +> **Presentation | [Manual](https://github.com/julien-boudry/Condorcet/wiki) | [Methods References](Documentation/README.md) | [Tests](Tests/)** + +Condorcet manages the stages of an electoral process (configuration, votes ingestion & manipulation, integrity) and calculates the results. It offers natively the implementation of more than 20 voting methods compatible with preferential voting ballots, including Condorcet methods, Alternative Voting, STV, and many others. +=> [**Supported Voting Methods**](#supported-voting-methods) + +_Two different ways to use Condorcet:_ +* A [**command line application**](#condorcet-wiki---command-line-manual), for quick use of essential features without complicated technical knowledge. Allowing you to easily compute your election results and stats. +* A [**PHP library**](#use-condorcet-as-a-php-library) that you can include in your code to take advantage of 100% of the advanced features (advanced manipulations & configurations, extensions & modularity, cache & high performances simulations, advanced input and output methods...). + +_Both approaches can handle up to hundreds of millions of votes (or more) on modest hardware. Although using it as a library will allow more configuration and control over this advanced usage._ + + +## Summary +1. [Project State and Specifications](#project-state-and-specifications) +1. [Supported Voting Methods](#supported-voting-methods) +1. [Main features](#main-features) + a. [Methods provided natively](#methods-provided-natively) + b. [Add your own method](#add-your-own-method-as-module) +1. [Use Condorcet as a command line application](#use-condorcet-as-a-command-line-application) + a. [Install as a command line application](#install-as-a-command-line-application) + b. [Condorcet Wiki - Command Line Manual](#condorcet-wiki---command-line-manual) + c. [Command Line - Examples](#command-line---some-quick-examples) +1. [Use Condorcet as a PHP Library](#use-condorcet-as-a-php-library) + a. [Install / Autoloading](#install--autoloading) + b. [Condorcet Wiki Manual](#condorcet-wiki-manual) + c. [Class & Methods reference](#class--methods-reference) + d. [PHP Library - Examples](#php-library---examples) *Have a look on Examples!* + e. [Really quick and simple example](#really-quick-and-simple-example) +1. [Performance & Coding style considerations](#roadmap-for-further-releases) +1. [Roadmap for further releases](#roadmap-for-further-releases) +1. [Related projects / They use Condorcet](#related-projects--they-use-condorcet) + +## Project State and Specifications + +> [**Releases Notes**](CHANGELOG.md) + +| Version | PHP Requirements | State | Support +| --- | --- | --- | --- | +| 4.2 | 8.1 | Stable | ✔ _support provided_ +| 4.1 | 8.1 | Old Stable | ✔ _support provided_ +| 3.x | 8.1 | Old Stable | ❌ _not any support_ +| 2.2 | 7.4 | Old Stable | ❌ _support requiring some bait_ +| 2.0 | 7.1 | Old Stable | ❌ _support requiring some bait_ +| 1.0 | 5.6 | Old Stable | ❌ _support requiring some bait_ +| 0.97 | 5.5 | Old Stable | ❌ _support requiring some bait_
ℹ _Since v0.90, you should consider then it's a new project (api, engine)._ +| 0.14 | 5.5 | Old Stable | ❌ _ready for the museum_ + +_All versions require Json and Mbstring extensions. Pdo-Sqlite is recommended if you need to activate the default provided driver for bigs elections (hundred of thousands of votes or more)_ + +## Supported Voting Methods +Support both single-winner methods _(with or without the Condorcet criterion)_ and proportional methods. + +[**Complete list of natively implemented methods, their options (variants), and implementation choices**](VOTING_METHODS.md) + +### Single-Winner Methods provided natively +Single Winner returns a full ranking of all candidates, even though they are generally more designed to designate only one. + +> Condorcet / Borda (+ Nauru variant) / Copeland / Dodgson (2 Approximations) / FTPT / Instant-runoff (alternative vote) / Kemeny–Young / Minimax (+ variants) / Ranked Pairs (+ variants) / Schulze (+ variants) + +### Proportional Methods provided natively +Designed for electing assembly, return a full ranking of elected candidates. + +> Single Transferable Vote *(STV)* / Comparison of Pairs of Outcomes by the Single Transferable Vote *(CPO-STV)* / Highest Averages Methods *(Sainte-Laguë, Jefferson/D'Hondt, and variants)* / Largest Remainder Methods _(with different quotas)_ + +### Add your own method as module +Condorcet is designed to be easily extensible with new algorithms (they don't need to share the same namespace). +[*More explanations in this documentation*](https://github.com/julien-boudry/Condorcet/wiki/III-%23-B.-Extending-Condorcet-%23-1.-Add-your-own-ranking-algorithm-%28library%29) + +## Main features +* __Manage an election__ + * Respect an election cycle: Registering candidates, registers votes, gets results from many algorithms. + * Ordered votes, tags votes, delete votes, simulates partial results. + * Many input types available _(string, Json, objects...)_ + * Import from Condorcet Election Format _(and export to)_, Debian Format, David Hill Format + * Integrity check (checksumming) + * Support for storing elections (serializing Election object, export data...) + * Some methods can be used nearly front final user (vote constraints, anti-spam check, parsing input, human-friendly results and stats...) +* __Get election results and stats__ + * Get the natural Condorcet Winner, Loser, Pairwise, Paradox... + * Get full ranking from advanced [voting methods](VOTING_METHODS.md) + * Get some additional stats from these methods + * Force ranking all candidates implicitly _(default)_ or allow voters to not rank all candidates. + * Put weight on a certain vote, and give more importance to certain voters. +* __Be more powerful__ + * All are objects, all are abstract _(But there are many higher-level functions and inputs types)_. + * Candidates and Votes are objects which can take part in multiple elections at the same time and change their name or ranking dynamically. That allows powerful tools to simulate elections. + * Manage hundred of billions of votes by activating an external driver to store (instead of RAM) an unlimited number of votes during the computation phase. A PDO driver is provided by default, an example is provided with SQLite, an interface that allows you to design other drivers. + * Smart cache system, allows calling multiple time computation methods without performance issues. +* __Extend it! Configure it!__ + * Modular architecture to extend it without fork Condorcet PHP! Just make your module on your own namespace. + * Election, Candidate, and Vote classes are extensible. + * Add your own ranking algorithm. + * Create your own vote constraints. + * Use your own datastore driver to manage very large elections on your way without ram limit. + * Many configuration options and methods. + +_Condorcet PHP is not designed for high performance. But can handle virtually unlimited voting without limit or degrading performance, it's a linear and predictable scheme._ +_And has no certification or proven implementation that would guarantee a very high level of reliability. However, there are many written tests for each voting method and feature. This ensures an excellent level of confidence in the results._ + + +--------------------------------------- +## Use Condorcet as a command line application + +### Install as a command line application + +Can be installed natively from source (with composer), from PHAR file, from Docker image (build or pull). + +> **[Condorcet as a command line application, installation instructions](https://github.com/julien-boudry/Condorcet/wiki/I-%23-Installation-%28command-line%29)** + +### Condorcet Wiki - Command Line Manual + +* [**Examples**](https://github.com/julien-boudry/Condorcet/wiki/III-%23-Usage-%28command-line%29) +* [**Man Page**](https://github.com/julien-boudry/Condorcet/wiki/II-%23-Man-Page-%28command-line%29) + +## Use Condorcet as a PHP Library + +### Install / Autoloading +Namespace ```\CondorcetPHP\Condorcet``` is used. + +Can be installed as you prefer with: Composer / Natively provided autoloader / Any PSR-4 compatible autoloader. + +> **[Condorcet as a PHP library, installation instruction](https://github.com/julien-boudry/Condorcet/wiki/I-%23-Installation---Basic-Configuration-%23-1.-Installation-%28library%29)** + +### Library Manual + +> **[Visit the Manual](https://github.com/julien-boudry/Condorcet/wiki)** + +Living and learning examples, giving an overview but not exhaustive of the possibilities of the library. + +### Class & Methods reference + +The precise documentation of methods is not a wiki. It can be found in the form of Markdown in the "Documentation" folder for each release. +> **[Class & Methods documentation](Documentation/README.md)** + +### PHP Library - Examples + +### Overview + +* [General Overview](Examples/1.%20Overview.php) _(not exhaustive and partial, but just a great tour.)_ +* [Advanced Object Management](Examples/1.%20AdvancedObjectManagement.php) + +### With Html output basics examples + +* [Visual simple & advanced script examples with HTML output](Examples/Examples-with-html/) + +### Specifics examples + +* [Condorcet Wiki Manual](https://github.com/julien-boudry/Condorcet/wiki) provides many code example +* [Manage millions of votes with an external database drive](Examples/Specifics_Examples/use_large_election_external_database_drivers.php) Your own driver, or the provided simple driver for PDO. + + +## Performance & Coding style considerations + +#### Coding standards: +The code is very close to the respect of PSR-12 (lacks only the naming of methods) when it is not unnecessarily authoritarian or conservative and follows some additional rules. Code is checked and fixed with [CS-Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer) custom rules through [Laravel Pint](https://github.com/laravel/pint). + +#### Performance: +* Complete and huge use case with all voting methods chained, 6 candidates, 2 seats, and one thousand votes (many with implicit ranking). + * _Memory usage: less than 3M_ + * _Execution time (after Jit compiling): less than 160ms_ + * _Execution time (without JIT): less than 250ms_ + +But essentially because some voting methods are slow by design and others (like Schulze) are very fast. Have a look on [methods benchmarks](Benchmarks/History/MethodsBench.md). + +###### Kemeny-Youg case: +_1 000 randoms votes. Memory consumption comes from votes more than combinations._ + +* use Kemeny-Young 7 candidates: ~5MB - 10ms +* use Kemeny-Young 8 candidates: ~6MB - 10ms +* use Kemeny-Young 9 candidates: ~7MB - 1.1s +* use Kemeny-Young 10 candidates: ~7MB - 14s +* use Kemeny-Young 11 candidates: ~8MB - 193s + +###### Massive election case: +Extending PHP memory_limit allows you to manage hundreds of thousands of votes, but it can be a bit slower than outsourcing this data (PHP doesn't like that) and it's not extensive to infinity. + +If you need to manage an election with more than 50 000 votes. You should consider externalizing your data, Condorcet provides a simple PDO driver to store data outside RAM between processing steps, this driver stores it into a classical relational database system, and it supports hundreds of millions of votes _(or more)_. A very simple example with Sqlite is provided and very easy to activate. + +You can also develop your homemade datastore driver (to store into NoSQL... all your fantasy), the modular architecture allows you to link it easily. + +[Have a look at the manual](https://github.com/julien-boudry/Condorcet/wiki/III-%23-A.-Avanced-features---Configuration-%23-3.-Get-started-to-handle-millions-of-votes-%28library%29) + +_Benchmark on a modern machine (linux - x64 - php 8.1 - cli)._ + + +## Roadmap for further releases +* ... + +## Related projects / They use Condorcet +* From August 2014: [Condorcet.Vote](http://www.condorcet.vote) Web services to create and store online Condorcet election. Including interactive and collaborative features. +It is based in large part on this project and uses the library as a real election manager for computing, storage & stats. +* [Mahler-S2-BlindTest-Condorcet +](https://github.com/julien-boudry/Mahler-S2-BlindTest-Condorcet) (French interface) Web wrapper to compute and show the result for classical music blind challenge with the Condorcet Class full potential (can also be used and adapted for any elections). +Look like the examples provided here, but better: [Gustav Mahler blind listening test](http://classik.forumactif.com/t7244-ecoute-comparee-mahler-2e-symphonie-la-suite) diff --git a/include/Condorcet/Tests/Examples/ExamplesTest.php b/include/Condorcet/Tests/Examples/ExamplesTest.php new file mode 100644 index 0000000..c4a1392 --- /dev/null +++ b/include/Condorcet/Tests/Examples/ExamplesTest.php @@ -0,0 +1,82 @@ + B > C + A > B > C * 4;tag1 || A > B > C*4 #Coucou + + A < B < C * 10 + D <> B + A > B > C diff --git a/include/Condorcet/Tests/README.md b/include/Condorcet/Tests/README.md new file mode 100644 index 0000000..f1044ab --- /dev/null +++ b/include/Condorcet/Tests/README.md @@ -0,0 +1,10 @@ +> **[Presentation](../README.md) | [Manual](https://github.com/julien-boudry/Condorcet/wiki) | [Methods References](../Documentation/README.md) | Tests** + +#### ~300 tests and more than 1000 assertions to explore on this path + +* The implementation tests proving the implementations of each method are in this directory **=>> [Algo Tests](lib/Algo/)** + +#### Execute the tests suite +``` +./vendor/bin/phpunit +``` \ No newline at end of file diff --git a/include/Condorcet/Tests/ReadmeQuickExampleTest.php b/include/Condorcet/Tests/ReadmeQuickExampleTest.php new file mode 100644 index 0000000..d5af9ed --- /dev/null +++ b/include/Condorcet/Tests/ReadmeQuickExampleTest.php @@ -0,0 +1,87 @@ +addCandidate($candidate1); + $myElection1->addCandidate($candidate2); + $myElection1->addCandidate($candidate3); + $candidate4 = $myElection1->addCandidate('Candidate 4'); + + // Add some votes, by some ways + $myElection1->addVote( + [ + $candidate2, // 1 + [$candidate1, $candidate4], // 2 - Tie + // Last rank is optionnal. Here it's : $candidate3 + ] + ); + + $myElection1->addVote('Candidate 2 > Candidate 3 > Candidate 4 = Candidate 1'); // last rank can also be omitted + + $myElection1->parseVotes( + 'tagX || Candidate 1 > Candidate 2 = Candidate 4 > Candidate 3 * 4 + tagX, tagY || Candidate 3 > Candidate 1 * 3' + ); // Powerfull, it add 7 votes + + $myElection1->addVote(new Vote( + [ + $candidate4, + $candidate2, + // You can ignore the over. They will be at the last rank in the contexte of each election. + ] + )); + + + // Get Result + + // Natural Condorcet Winner + $myWinner = $myElection1->getCondorcetWinner(); // Return a candidate object + $this->assertEquals('My winner is Candidate 1
', 'My winner is ' . $myWinner->getName() . '
'); + + // Natural Condorcet Loser + $myLoser = $myElection1->getCondorcetLoser(); // Return a candidate object + $this->assertEquals('My loser is Candidate 3', 'My loser is ' . $myLoser->getName()); + + // Schulze Ranking + $myResultBySchulze = $myElection1->getResult('Schulze'); // Return a multi-dimensional array, filled with objects Candidate (multi-dimensional if tie on a rank) + # Echo it easily + $this->assertEquals([1=>'Candidate 1', 2=>'Candidate 2', 3=>'Candidate 4', 4=>'Candidate 3'], CondorcetUtil::format($myResultBySchulze)); + + // Get Schulze advanced computing data & stats + $mySchulzeStats = $myElection1->getResult('Schulze')->getStats(); + + // Get Copeland Ranking + $myResultByCopeland = $myElection1->getResult('Copeland'); + + // Get Pairwise + $myPairwise = $myElection1->getPairwise(); + + + // How long computation time behind us? + $timer = $myElection1->getGlobalTimer(); + + // SHA-2 checksum and sleep + $myChecksum = $myElection1->getChecksum(); + $toStore = serialize($myElection1); + $comeBack = unserialize($toStore); + $this->assertEquals($comeBack->getChecksum(), $myChecksum); // True + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Borda/BordaCountTest.php b/include/Condorcet/Tests/src/Algo/Methods/Borda/BordaCountTest.php new file mode 100644 index 0000000..4d469a1 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Borda/BordaCountTest.php @@ -0,0 +1,220 @@ +election = new Election; + } + + protected function tearDown(): void + { + $this->election->setMethodOption('Borda Count', 'Starting', 1); + } + + public function testResult_1(): void + { + # From https://fr.wikipedia.org/wiki/M%C3%A9thode_Borda + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D * 42 + B>C>D>A * 26 + C>D>B>A * 15 + D>C>B>A * 17 + '); + + self::assertSame( + [ + 1 => 'B', + 2 => 'C', + 3 => 'A', + 4 => 'D', ], + $this->election->getResult('Borda Count')->getResultAsArray(true) + ); + + self::assertEquals( + [ + 'B' => 294, + 'C' => 273, + 'A' => 226, + 'D' => 207, ], + $this->election->getResult('Borda Count')->getStats() + ); + } + + public function testResult_2(): void + { + # From https://fr.wikipedia.org/wiki/M%C3%A9thode_Borda + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + B>A>C>D * 30 + B>A>D>C * 30 + A>C>D>B * 25 + A>D>C>B ^ 15 + '); + + self::assertSame( + [ + 1 => 'A', + 2 => 'B', + 3 => 'C', + 4 => 'D', ], + $this->election->getResult('Borda Count')->getResultAsArray(true) + ); + + self::assertEquals( + [ + 'A' => 340, + 'B' => 280, + 'C' => 195, + 'D' => 185, ], + $this->election->getResult('Borda Count')->getStats() + ); + } + + public function testResult_3(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A + '); + + self::assertSame( + [ + 1 => 'A', + 2 => ['B', 'C'], ], + $this->election->getResult('Borda Count')->getResultAsArray(true) + ); + + self::assertEquals( + [ + 'A' => 3, + 'B' => 1.5, + 'C' => 1.5, ], + $this->election->getResult('Borda Count')->getStats() + ); + + $this->election->setImplicitRanking(false); + + self::assertSame( + [ + 1 => 'A', + 2 => ['B', 'C'], ], + $this->election->getResult('Borda Count')->getResultAsArray(true) + ); + + self::assertEquals( + [ + 'A' => 3, + 'B' => 0, + 'C' => 0, ], + $this->election->getResult('Borda Count')->getStats() + ); + } + + public function testResult_4(): void + { + # From https://fr.wikipedia.org/wiki/M%C3%A9thode_Borda + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D * 42 + B>C>D>A * 26 + C>D>B>A * 15 + D>C>B>A * 17 + '); + + self::assertSame( + [ + 1 => 'B', + 2 => 'C', + 3 => 'A', + 4 => 'D', ], + $this->election->getResult('Borda Count')->getResultAsArray(true) + ); + + self::assertEquals( + [ + 'B' => 294, + 'C' => 273, + 'A' => 226, + 'D' => 207, ], + $this->election->getResult('Borda Count')->getStats() + ); + } + + public function testResult_variant(): void + { + # From https://fr.wikipedia.org/wiki/M%C3%A9thode_Borda + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D * 42 + B>C>D>A * 26 + C>D>B>A * 15 + D>C>B>A * 17 + '); + + $this->election->setMethodOption('Borda Count', 'Starting', 0); + + self::assertSame( + [ + 1 => 'B', + 2 => 'C', + 3 => 'A', + 4 => 'D', ], + $this->election->getResult('Borda Count')->getResultAsArray(true) + ); + + self::assertEquals( + [ + 'B' => 294 - 100, + 'C' => 273 - 100, + 'A' => 226 - 100, + 'D' => 207 - 100, ], + $this->election->getResult('Borda Count')->getStats() + ); + } + + public function testVeryHighVoteWeightAndPerformances(): void + { + $this->election->allowsVoteWeight(true); + $this->election->parseCandidates('0;1'); + + $this->election->parseVotes('1 > 0 ^6973568802'); + + self::assertSame('1', $this->election->getResult('Borda Count')->getResultAsString()); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Borda/DowdallSystemTest.php b/include/Condorcet/Tests/src/Algo/Methods/Borda/DowdallSystemTest.php new file mode 100644 index 0000000..c5bf4e0 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Borda/DowdallSystemTest.php @@ -0,0 +1,58 @@ +election = new Election; + } + + public function testResult_1(): void + { + # From https://en.wikipedia.org/wiki/Borda_count + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + $this->election->addCandidate('F'); + + $this->election->parseVotes(' + A>B>C>D>E>F + '); + + self::assertSame( + [ + 1 => 'A', + 2 => 'B', + 3 => 'C', + 4 => 'D', + 5 => 'E', + 6 => 'F', ], + $this->election->getResult('DowdallSystem')->getResultAsArray(true) + ); + + self::assertEqualsWithDelta( + [ + 'A' => 1/1, + 'B' => 1/2, + 'C' => 1/3, + 'D' => 1/4, + 'E' => 1/5, + 'F' => 1/6, ], + $this->election->getResult('DowdallSystem')->getStats(), + 1 / (0.1 ** DowdallSystem::DECIMAL_PRECISION) + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/CondorcetBasicTest.php b/include/Condorcet/Tests/src/Algo/Methods/CondorcetBasicTest.php new file mode 100644 index 0000000..a9b6a9d --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/CondorcetBasicTest.php @@ -0,0 +1,161 @@ +election = new Election; + } + + public function testResult_Basic(): void + { + $this->election->addCandidate('a'); + $this->election->addCandidate('b'); + $this->election->addCandidate('c'); + + $vote = new Vote('a'); + + $this->election->addVote($vote); + + self::assertEquals('a', $this->election->getCondorcetWinner()); + } + + public function testResult_1(): void + { + $this->election->addCandidate('a'); + $this->election->addCandidate('b'); + $this->election->addCandidate('c'); + + $this->election->parseVotes(' + a > c > b * 23 + b > c > a * 19 + c > b > a * 16 + c > a > b * 2 + '); + + self::assertEquals('c', $this->election->getCondorcetWinner()); + } + + public function testResult_2(): void + { + $this->election->addCandidate('X'); + $this->election->addCandidate('Y'); + $this->election->addCandidate('Z'); + + $this->election->parseVotes(' + X > Y > Z * 41 + Y > Z > X * 33 + Z > X > Y * 22 + '); + + self::assertNull($this->election->getWinner()); + + // Schulze Substitution + self::assertEquals('X', $this->election->getWinner('Schulze')); + } + + public function testResult_3(): void + { + $this->election->addCandidate('Memphis'); + $this->election->addCandidate('Nashville'); + $this->election->addCandidate('Knoxville'); + $this->election->addCandidate('Chattanooga'); + + $this->election->parseVotes(' + Memphis > Nashville > Chattanooga * 42 + Nashville > Chattanooga > Knoxville * 26 + Chattanooga > Knoxville > Nashville * 15 + Knoxville > Chattanooga > Nashville * 17 + '); + + self::assertEquals('Nashville', $this->election->getCondorcetWinner()); + self::assertEquals('Memphis', $this->election->getCondorcetLoser()); + } + + public function testResult_4(): void + { + $this->election->addCandidate('Memphis'); + $this->election->addCandidate('Nashville'); + $this->election->addCandidate('Knoxville'); + $this->election->addCandidate('Chattanooga'); + + $this->election->parseVotes(' + Memphis > Chattanooga > Nashville * 42 + Nashville > Chattanooga > Knoxville * 26 + Chattanooga > Knoxville > Nashville * 15 + Knoxville > Chattanooga > Nashville * 17 + '); + + self::assertEquals('Chattanooga', $this->election->getCondorcetWinner()); + } + + public function testResult_5(): void + { + # From https://en.wikipedia.org/wiki/Condorcet_loser_criterion + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('L'); + + $this->election->parseVotes(' + A > B > C * 1 + A > B > L * 1 + B > C > A * 3 + C > L > A * 1 + L > A > B * 1 + L > C > A * 2 + '); + + self::assertEquals('L', $this->election->getCondorcetLoser()); + self::assertNull($this->election->getCondorcetWinner()); + } + + public function testResult_6(): void + { + # From https://en.wikipedia.org/wiki/Condorcet_loser_criterion + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('L'); + + $this->election->parseVotes(' + A > B > L + B > C > L + A > C > L + '); + + self::assertEquals('L', $this->election->getCondorcetLoser()); + } + + public function testNoResultObject(): never + { + $this->expectException(AlgorithmWithoutRankingFeatureException::class); + $this->expectExceptionMessage("This algortihm can't provide a full ranking (but only Winner and Loser): ".CondorcetBasic::METHOD_NAME[0]); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('L'); + + $this->election->parseVotes(' + A > B > L + B > C > L + A > C > L + '); + + $this->election->getResult(CondorcetBasic::class); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Copeland/CopelandTest.php b/include/Condorcet/Tests/src/Algo/Methods/Copeland/CopelandTest.php new file mode 100644 index 0000000..51b2b70 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Copeland/CopelandTest.php @@ -0,0 +1,130 @@ +election = new Election; + } + + public function testResult_1(): void + { + # From https://en.wikipedia.org/wiki/Copeland%27s_method + + $this->election->addCandidate('Memphis'); + $this->election->addCandidate('Nashville'); + $this->election->addCandidate('Knoxville'); + $this->election->addCandidate('Chattanooga'); + + $this->election->parseVotes(' + Memphis > Nashville > Chattanooga * 42 + Nashville > Chattanooga > Knoxville * 26 + Chattanooga > Knoxville > Nashville * 15 + Knoxville > Chattanooga > Nashville * 17 + '); + + + self::assertSame( + [ + 1 => 'Nashville', + 2 => 'Chattanooga', + 3 => 'Knoxville', + 4 => 'Memphis', ], + $this->election->getResult('Copeland')->getResultAsArray(true) + ); + + self::assertSame($this->election->getWinner('Copeland'), $this->election->getWinner()); + + self::assertSame( + [ + 'Memphis' => [ + 'balance' => -3, + ], + 'Nashville' => [ + 'balance' => 3, + ], + 'Knoxville' => [ + 'balance' => -1, + ], + 'Chattanooga' => [ + 'balance' => 1, + ], + ], + $this->election->getResult('Copeland')->getStats() + ); + } + + public function testResult_2(): void + { + # From https://en.wikipedia.org/wiki/Copeland%27s_method + + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + $candidateE = $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A > E > C > D * 31 + B > A > E * 30 + C > D > B * 29 + D > A > E * 10 + '); + + self::assertNull($this->election->getWinner()); + self::assertSame($candidateA, $this->election->getWinner('Copeland')); + + self::assertSame( + [1 => $candidateA, + 2 => [$candidateB, $candidateC, $candidateE], + 3 => $candidateD, + ], + $this->election->getResult('Copeland')->getResultAsArray() + ); + } + + public function testResult_3(): void + { + # From http://www.cs.wustl.edu/~legrand/rbvote/desc.html + + $this->election->addCandidate('Abby'); + $this->election->addCandidate('Brad'); + $this->election->addCandidate('Cora'); + $this->election->addCandidate('Dave'); + $this->election->addCandidate('Erin'); + + $this->election->parseVotes(' + Abby>Cora>Erin>Dave>Brad * 98 + Brad>Abby>Erin>Cora>Dave * 64 + Brad>Abby>Erin>Dave>Cora * 12 + Brad>Erin>Abby>Cora>Dave * 98 + Brad>Erin>Abby>Dave>Cora * 13 + Brad>Erin>Dave>Abby>Cora * 125 + Cora>Abby>Erin>Dave>Brad * 124 + Cora>Erin>Abby>Dave>Brad * 76 + Dave>Abby>Brad>Erin>Cora * 21 + Dave>Brad>Abby>Erin>Cora * 30 + Dave>Brad>Erin>Cora>Abby * 98 + Dave>Cora>Abby>Brad>Erin * 139 + Dave>Cora>Brad>Abby>Erin * 23 + '); + + self::assertEquals(['Abby', 'Brad'], $this->election->getWinner('Copeland')); + + self::assertSame( + [1 => ['Abby', 'Brad'], + 2 => ['Dave', 'Erin'], + 3 => 'Cora', ], + $this->election->getResult('Copeland')->getResultAsArray(true) + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Dodgson/DodgsonTest.php b/include/Condorcet/Tests/src/Algo/Methods/Dodgson/DodgsonTest.php new file mode 100644 index 0000000..2a8cd47 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Dodgson/DodgsonTest.php @@ -0,0 +1,534 @@ +election = new Election; + } + + public function testResult_1(): void + { + # From http://www.cs.wustl.edu/~legrand/rbvote/desc.html + + $CandidateCora = $this->election->addCandidate('Cora'); + $this->election->addCandidate('Abby'); + $this->election->addCandidate('Brad'); + $this->election->addCandidate('Dave'); + $this->election->addCandidate('Erin'); + + $this->election->parseVotes(' + Abby>Cora>Erin>Dave>Brad * 98 + Brad>Abby>Erin>Cora>Dave * 64 + Brad>Abby>Erin>Dave>Cora * 12 + Brad>Erin>Abby>Cora>Dave * 98 + Brad>Erin>Abby>Dave>Cora * 13 + Brad>Erin>Dave>Abby>Cora * 125 + Cora>Abby>Erin>Dave>Brad * 124 + Cora>Erin>Abby>Dave>Brad * 76 + Dave>Abby>Brad>Erin>Cora * 21 + Dave>Brad>Abby>Erin>Cora * 30 + Dave>Brad>Erin>Cora>Abby * 98 + Dave>Cora>Abby>Brad>Erin * 139 + Dave>Cora>Brad>Abby>Erin * 23 + '); + + self::assertSame($CandidateCora, $this->election->getWinner('DodgsonTideman')); + + self::assertSame( + [1 => 'Cora', + 2 => 'Abby', + 3 => 'Brad', + 4 => 'Dave', + 5 => 'Erin', ], + $this->election->getResult('DodgsonTideman')->getResultAsArray(true) + ); + + self::assertSame( + [ + 'Cora' => [ + 'sum_defeat_margin' => 4, + ], + 'Abby' => [ + 'sum_defeat_margin' => 5, + ], + 'Brad' => [ + 'sum_defeat_margin' => 297, + ], + 'Dave' => [ + 'sum_defeat_margin' => 348, + ], + 'Erin' => [ + 'sum_defeat_margin' => 426, + ], + ], + $this->election->getResult('DodgsonTideman')->getStats() + ); + } + + # Require real Dodgson method. This test fail with both approximations. + // public function testResult_2 (): void + // { + // # From http://dss.in.tum.de/files/brandt-research/dodgson.pdf + # Table 1 + + // $this->election->addCandidate('A'); + // $this->election->addCandidate('B'); + // $this->election->addCandidate('C'); + // $this->election->addCandidate('D'); + + // $this->election->parseVotes(' + // D>C>A>B*2 + // B>C>A>D*2 + // C>A>B>D*2 + // D>B>C>A*2 + // A>B>C>D*2 + // A>D>B>C*1 + // D>A>B>C*1 + // '); + + // self::assertEquals( + // 'A', $this->election->getWinner('DodgsonQuick')); + // } + + public function testResult_3(): void + { + # From http://dss.in.tum.de/files/brandt-research/dodgson.pdf + # Table 2 + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + D>C>A>B*6 + B>C>A>D*6 + C>A>B>D*6 + D>B>C>A*6 + A>B>C>D*6 + A>D>B>C*3 + D>A>B>C*3 + '); + + self::assertEquals( + 'D', + $this->election->getWinner('DodgsonQuick') + ); + + self::assertSame( + [ + 'D' => 3.0, + 'A' => 6.0, + 'B' => 6.0, + 'C' => 6.0, + ], + $this->election->getResult('DodgsonQuick')->getStats() + ); + } + + # Require real Dodgson method. This test fail with both approximations. + // public function testResult_4 (): void + // { + // # From http://dss.in.tum.de/files/brandt-research/dodgson.pdf + # Table 3 + + // $this->election->addCandidate('A'); + // $this->election->addCandidate('B'); + // $this->election->addCandidate('C'); + // $this->election->addCandidate('D'); + + // $this->election->parseVotes(' + // C>A>D>B*15 + // B>D>C>A*9 + // A>B>D>C*9 + // A>C>B>D*5 + // B>A>C>D*5 + // '); + + // self::assertEquals( + // 'A', $this->election->getWinner('DodgsonQuick')); + // } + + public function testResult_5(): void + { + # From http://dss.in.tum.de/files/brandt-research/dodgson.pdf + # Table 4 + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + C>A>D>B*15 + B>D>C>A*9 + A>B>D>C*9 + A>C>B>D*5 + A>B>C>D*5 + '); + + self::assertEquals( + 'C', + $this->election->getWinner('DodgsonQuick') + ); + + self::assertSame( + ['C' => 2.0, + 'A' => 3.0, + 'B' => 13.0, + 'D' => 24.0, + ], + $this->election->getResult('DodgsonQuick')->getStats() + ); + } + + public function testResult_6(): void + { + # From http://dss.in.tum.de/files/brandt-research/dodgson.pdf + # Table 5 + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + D>A>B>C*10 + B>C>A>D*8 + C>A>B>D*7 + D>C>A>B*4 + '); + + self::assertEquals( + 'D', + $this->election->getWinner('DodgsonQuick') + ); + + self::assertSame( + ['D' => 3.0, + 'C' => 4.0, + 'A' => 5.0, + 'B' => 7.0, + ], + $this->election->getResult('DodgsonQuick')->getStats() + ); + } + + public function testResult_7(): void + { + # From http://dss.in.tum.de/files/brandt-research/dodgson.pdf + # Table 6 + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + C>B>A>D*10 + D>A>C>B*8 + D>B>A>C*7 + B>A>C>D*4 + '); + + self::assertEquals( + 'D', + $this->election->getWinner('DodgsonQuick') + ); + + self::assertSame( + ['B' => 5.0, + 'C' => 6.0, + 'A' => 8.0, + ], + $this->election->getResult('DodgsonQuick')->getStats() + ); + } + + public function testResult_8(): void + { + # From http://dss.in.tum.de/files/brandt-research/dodgson.pdf + # Table 7 + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A>B>C*5 + B>C>A*4 + C>A>B*3 + '); + + self::assertEquals( + 'A', + $this->election->getWinner('DodgsonQuick') + ); + + self::assertSame( + ['A' => 1.0, + 'B' => 2.0, + 'C' => 3.0, + ], + $this->election->getResult('DodgsonQuick')->getStats() + ); + } + + # Require real Dodgson method. This test fail with both approximations. + // public function testResult_9 (): void + // { + // # From http://dss.in.tum.de/files/brandt-research/dodgson.pdf + # Table 8 + + // $this->election->addCandidate('A'); + // $this->election->addCandidate('B'); + // $this->election->addCandidate('C'); + // $this->election->addCandidate('Cp'); + + // $this->election->parseVotes(' + // A>B>C>Cp*5 + // B>C>Cp>A*4 + // C>Cp>A>B*3 + // '); + + // self::assertEquals( + // 'B', $this->election->getWinner('DodgsonQuick')); + // } + + public function testResult_10(): void + { + # From https://link.springer.com/article/10.1007/s003550000060 + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D*21 + C>D>B>A*12 + D>C>A>B*5 + B>D>A>C*12 + '); + + self::assertEquals( + 'B', + $this->election->getWinner('DodgsonQuick') + ); + + self::assertEquals( + 'B', + $this->election->getWinner('DodgsonTideman') + ); + } + + public function testResult_11(): void + { + # From https://www.maa.org/sites/default/files/pdf/cmj_ftp/CMJ/September%202010/3%20Articles/6%2009-229%20Ratliff/Dodgson_CMJ_Final.pdf + # Figure 2 with Tideman Approximation + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D*3 + D>B>A>C*1 + D>C>A>B*1 + B>D>C>A*1 + C>D>B>A*1 + '); + + self::assertEquals(1, $this->election->getResult('DodgsonTideman')->getStats()['A']['sum_defeat_margin']); + self::assertEquals(1, $this->election->getResult('DodgsonTideman')->getStats()['B']['sum_defeat_margin']); + self::assertEquals(4, $this->election->getResult('DodgsonTideman')->getStats()['C']['sum_defeat_margin']); + self::assertEquals(2, $this->election->getResult('DodgsonTideman')->getStats()['D']['sum_defeat_margin']); + + self::assertSame( + [1 => ['A', 'B'], + 2 => 'D', + 3 => 'C', + ], + $this->election->getResult('DodgsonTideman')->getResultAsArray(true) + ); + } + + public function testResult_12(): void + { + # From https://www.maa.org/sites/default/files/pdf/cmj_ftp/CMJ/September%202010/3%20Articles/6%2009-229%20Ratliff/Dodgson_CMJ_Final.pdf + # Figure 3 with Tideman Approximation + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D*5 + D>C>A>B*6 + C>A>B>D*5 + D>B>C>A*5 + B>C>A>D*4 + D>A>B>C*4 + C>D>A>B*1 + B>A>C>D*1 + B>D>A>C*1 + C>A>B>D*1 + A>D>B>C*1 + C>B>A>D*1 + '); + + self::assertEquals(11, $this->election->getResult('DodgsonTideman')->getStats()['A']['sum_defeat_margin']); + self::assertEquals(11, $this->election->getResult('DodgsonTideman')->getStats()['B']['sum_defeat_margin']); + self::assertEquals(7, $this->election->getResult('DodgsonTideman')->getStats()['C']['sum_defeat_margin']); + self::assertEquals(3, $this->election->getResult('DodgsonTideman')->getStats()['D']['sum_defeat_margin']); + + + self::assertEquals( + 'D', + $this->election->getWinner('DodgsonTideman') + ); + + self::assertSame( + [1 => 'D', + 2 => 'C', + 3 => ['A', 'B'], + ], + $this->election->getResult('DodgsonTideman')->getResultAsArray(true) + ); + } + + public function testResult_13(): void + { + # From https://www.maa.org/sites/default/files/pdf/cmj_ftp/CMJ/September%202010/3%20Articles/6%2009-229%20Ratliff/Dodgson_CMJ_Final.pdf + # Figure 4 + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + $this->election->addCandidate('F'); + + $this->election->parseVotes(' + A>B>C>D>E>F*19 + F>A>B>C>D>E*12 + E>D>C>B>F>A*12 + B>A>C>D>E>F*9 + F>E>D>C>B>A*9 + F>B>A>C>D>E*10 + E>D>C>A>F>B*10 + E>B>A>C>D>F*10 + F>D>C>A>E>B*10 + D>B>A>C>E>F*10 + F>E>C>A>D>B*10 + '); + + self::assertEquals( + 'A', + $this->election->getWinner('DodgsonQuick') + ); + + self::assertSame( + ['A' => 3.0, + 'B' => 4.0, + 'C' => 20.0, + 'D' => 20.0, + 'E' => 30.0, + 'F' => 30.0, + ], + $this->election->getResult('DodgsonQuick')->getStats() + ); + } + + public function testResult_14(): void + { + # From https://www.maa.org/sites/default/files/pdf/cmj_ftp/CMJ/September%202010/3%20Articles/6%2009-229%20Ratliff/Dodgson_CMJ_Final.pdf + # Figure 4: each voters add 4 friends. + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + $this->election->addCandidate('F'); + + $this->election->parseVotes(' + A>B>C>D>E>F*95 + F>A>B>C>D>E*60 + E>D>C>B>F>A*60 + B>A>C>D>E>F*45 + F>E>D>C>B>A*45 + F>B>A>C>D>E*50 + E>D>C>A>F>B*50 + E>B>A>C>D>F*50 + F>D>C>A>E>B*50 + D>B>A>C>E>F*50 + F>E>C>A>D>B*50 + '); + + self::assertEquals( + 13, + $this->election->getResult('DodgsonQuick')->getStats()['A'] + ); + + self::assertEquals( + 12, + $this->election->getResult('DodgsonQuick')->getStats()['B'] + ); + + self::assertEquals( + 'B', + $this->election->getWinner('DodgsonQuick') + ); + } + + public function testResult_15(): void + { + $this->election->addCandidate('Memphis'); + $this->election->addCandidate('Nashville'); + $this->election->addCandidate('Knoxville'); + $this->election->addCandidate('Chattanooga'); + + $this->election->parseVotes(' + Memphis > Chattanooga > Nashville * 42 + Nashville > Chattanooga > Knoxville * 26 + Chattanooga > Knoxville > Nashville * 15 + Knoxville > Chattanooga > Nashville * 17 + '); + + self::assertSame($this->election->getWinner(null), $this->election->getWinner('DodgsonQuick')); + } + + public function testResult_16(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A + B + '); + + self::assertSame( + [ + 1 => ['A', 'B'], + 2 => ['C', 'D'], + ], + $this->election->getResult('DodgsonQuick')->getResultAsArray(true) + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/A03.cvotes b/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/A03.cvotes new file mode 100644 index 0000000..d9a0043 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/A03.cvotes @@ -0,0 +1,736 @@ +# Specifications: https://github.com/CondorcetPHP/CondorcetElectionFormat + +# This election has 989 votes +#/Candidates: 1 ; 2 ; 3 ; 4 ; 5 ; 6 ; 7 ; 8 ; 9 ; 10 ; 11 ; 12 ; 13 ; 14 ; 15 +#/Number of Seats: 7 +#/Implicit Ranking: true +#/Weight Allowed: false + +6 > 5 > 4 > 11 > 7 > 10 > 12 > 13 > 8 > 9 > 14 > 15 * 30 +6 * 14 +4 * 13 +8 * 13 +2 * 11 +/EMPTY_RANKING/ * 8 +8 > 11 * 8 +2 > 7 * 6 +6 > 8 > 14 * 6 +8 > 5 > 11 > 9 * 6 +8 > 6 * 6 +14 * 6 +5 * 5 +6 > 8 * 5 +8 > 11 > 5 * 5 +8 > 12 * 5 +11 * 5 +1 > 3 > 13 * 4 +6 > 8 > 11 * 4 +6 > 8 > 12 * 4 +6 > 11 > 14 * 4 +8 > 6 > 14 * 4 +12 * 4 +1 * 3 +2 > 1 * 3 +2 > 7 > 9 * 3 +3 * 3 +4 > 6 > 5 * 3 +4 > 6 > 8 * 3 +5 > 6 > 8 * 3 +6 > 4 > 8 * 3 +6 > 4 > 11 * 3 +6 > 8 > 4 * 3 +6 > 11 * 3 +7 * 3 +8 > 5 * 3 +8 > 5 > 9 > 11 * 3 +8 > 6 > 11 * 3 +8 > 6 > 12 * 3 +8 > 11 > 5 > 9 * 3 +8 > 14 * 3 +9 * 3 +11 > 6 > 4 * 3 +11 > 14 > 6 * 3 +14 > 8 * 3 +1 > 2 > 7 * 2 +1 > 4 > 6 * 2 +1 > 6 * 2 +1 > 13 > 3 * 2 +2 > 5 > 10 * 2 +2 > 9 > 10 * 2 +2 > 10 > 9 * 2 +3 > 8 * 2 +4 > 5 > 3 * 2 +4 > 6 * 2 +4 > 6 > 3 * 2 +4 > 6 > 11 * 2 +4 > 8 > 6 * 2 +4 > 8 > 11 * 2 +4 > 11 * 2 +4 > 14 > 11 * 2 +5 > 6 > 11 * 2 +5 > 6 > 14 * 2 +5 > 8 * 2 +5 > 11 * 2 +5 > 11 > 4 * 2 +5 > 11 > 6 * 2 +5 > 11 > 8 * 2 +6 > 3 > 5 * 2 +6 > 4 > 8 > 12 > 11 > 14 > 3 > 5 > 13 * 2 +6 > 4 > 10 > 14 > 7 > 5 > 8 > 11 * 2 +6 > 4 > 12 * 2 +6 > 5 > 4 > 14 > 7 > 10 > 12 > 13 > 8 > 9 > 11 > 15 * 2 +6 > 5 > 8 > 11 * 2 +6 > 5 > 11 * 2 +6 > 11 > 12 * 2 +6 > 11 > 15 * 2 +6 > 12 * 2 +7 > 10 > 6 * 2 +8 > 5 > 6 > 11 * 2 +8 > 5 > 11 * 2 +8 > 5 > 11 > 12 * 2 +8 > 6 > 5 * 2 +8 > 9 > 12 * 2 +8 > 11 > 9 > 5 * 2 +8 > 11 > 14 * 2 +8 > 14 > 5 * 2 +9 > 10 > 12 > 6 > 14 > 13 > 1 * 2 +10 * 2 +11 > 4 > 6 * 2 +11 > 4 > 8 * 2 +11 > 4 > 14 > 7 > 8 > 3 * 2 +11 > 5 > 3 * 2 +11 > 5 > 6 * 2 +11 > 5 > 6 > 14 * 2 +11 > 6 > 5 * 2 +11 > 6 > 5 > 8 * 2 +11 > 6 > 12 * 2 +11 > 7 * 2 +11 > 8 * 2 +11 > 8 > 6 * 2 +11 > 12 > 14 * 2 +12 > 8 * 2 +12 > 14 * 2 +14 > 4 > 6 * 2 +14 > 5 * 2 +14 > 6 > 8 * 2 +14 > 6 > 10 * 2 +14 > 11 > 6 * 2 +1 > 2 > 3 > 4 > 5 > 6 > 7 > 8 > 9 > 10 > 11 > 12 > 13 > 14 * 1 +1 > 2 > 3 > 6 > 10 * 1 +1 > 2 > 3 > 7 > 8 > 15 > 10 > 13 * 1 +1 > 2 > 15 > 9 * 1 +1 > 3 > 2 > 15 > 6 * 1 +1 > 3 > 5 > 13 > 7 > 11 * 1 +1 > 3 > 7 * 1 +1 > 3 > 10 * 1 +1 > 3 > 13 > 4 * 1 +1 > 3 > 14 * 1 +1 > 3 > 15 * 1 +1 > 4 > 5 > 13 * 1 +1 > 4 > 6 > 11 > 12 > 13 > 15 > 8 > 14 > 7 > 5 > 2 > 9 > 10 * 1 +1 > 4 > 6 > 13 > 11 > 14 * 1 +1 > 5 * 1 +1 > 6 > 2 > 15 > 11 > 7 > 3 > 10 * 1 +1 > 6 > 4 > 11 * 1 +1 > 6 > 8 > 9 > 15 * 1 +1 > 7 * 1 +1 > 7 > 15 * 1 +1 > 8 > 4 > 11 * 1 +1 > 8 > 5 * 1 +1 > 8 > 12 * 1 +1 > 9 > 2 > 3 > 6 > 5 * 1 +1 > 9 > 10 > 13 > 3 > 5 * 1 +1 > 10 > 2 > 3 > 7 * 1 +1 > 10 > 2 > 8 * 1 +1 > 11 > 13 * 1 +1 > 13 > 2 > 3 > 7 * 1 +1 > 13 > 5 > 4 > 6 * 1 +1 > 14 > 15 > 11 * 1 +1 > 15 > 4 > 6 > 7 > 8 > 3 > 2 > 9 > 10 > 13 > 11 > 14 > 12 * 1 +2 > 1 > 4 * 1 +2 > 1 > 4 > 13 * 1 +2 > 1 > 7 * 1 +2 > 1 > 7 > 15 * 1 +2 > 1 > 10 * 1 +2 > 3 > 1 > 7 > 8 > 9 > 13 > 5 > 12 > 15 > 10 > 14 > 4 > 11 * 1 +2 > 3 > 1 > 13 * 1 +2 > 3 > 5 > 11 * 1 +2 > 6 * 1 +2 > 6 > 7 > 10 * 1 +2 > 6 > 7 > 10 > 1 > 13 > 3 * 1 +2 > 6 > 7 > 10 > 9 * 1 +2 > 6 > 10 * 1 +2 > 7 > 1 > 3 > 10 > 13 * 1 +2 > 7 > 1 > 6 > 10 > 15 * 1 +2 > 7 > 5 > 1 > 3 > 10 > 9 * 1 +2 > 7 > 9 > 1 > 3 * 1 +2 > 7 > 9 > 13 > 10 * 1 +2 > 7 > 10 > 9 > 8 * 1 +2 > 7 > 10 > 9 > 13 > 3 > 8 > 6 * 1 +2 > 7 > 12 > 13 > 9 > 10 > 6 * 1 +2 > 8 * 1 +2 > 8 > 6 * 1 +2 > 8 > 15 > 11 * 1 +2 > 9 > 6 * 1 +2 > 10 > 6 * 1 +2 > 10 > 7 > 9 * 1 +2 > 13 * 1 +2 > 13 > 6 > 3 > 8 * 1 +2 > 13 > 7 * 1 +2 > 13 > 9 > 10 > 7 * 1 +3 > 1 * 1 +3 > 1 > 7 > 13 > 15 * 1 +3 > 2 * 1 +3 > 2 > 7 > 15 * 1 +3 > 2 > 7 > 15 > 13 * 1 +3 > 4 > 10 * 1 +3 > 4 > 10 > 14 * 1 +3 > 4 > 12 > 13 > 11 > 8 > 9 * 1 +3 > 6 > 4 * 1 +3 > 6 > 4 > 1 * 1 +3 > 6 > 7 * 1 +3 > 6 > 11 * 1 +3 > 7 > 11 * 1 +3 > 8 > 6 * 1 +3 > 9 > 13 * 1 +3 > 11 > 6 > 10 > 14 * 1 +3 > 11 > 8 * 1 +3 > 13 * 1 +3 > 13 > 1 > 7 > 15 > 2 > 9 > 10 * 1 +3 > 14 > 5 > 10 > 13 * 1 +3 > 14 > 6 > 8 * 1 +4 > 1 > 5 * 1 +4 > 1 > 6 > 9 > 11 > 3 > 5 > 2 > 7 > 10 > 12 > 14 > 15 > 13 * 1 +4 > 1 > 6 > 11 * 1 +4 > 1 > 6 > 11 > 2 > 8 * 1 +4 > 2 > 7 > 8 > 5 > 13 > 14 > 9 > 10 > 3 > 1 > 6 > 15 > 11 * 1 +4 > 3 > 1 > 5 * 1 +4 > 3 > 5 > 8 > 14 * 1 +4 > 3 > 6 > 8 > 11 * 1 +4 > 3 > 13 * 1 +4 > 5 * 1 +4 > 5 > 6 > 7 > 11 > 14 * 1 +4 > 5 > 6 > 8 > 3 > 11 * 1 +4 > 5 > 6 > 8 > 11 > 14 > 15 > 13 > 12 > 3 > 1 > 2 > 7 > 9 * 1 +4 > 5 > 6 > 11 * 1 +4 > 5 > 6 > 11 > 12 > 15 * 1 +4 > 5 > 6 > 11 > 14 * 1 +4 > 5 > 6 > 11 > 14 > 12 > 8 > 7 > 15 > 3 > 10 * 1 +4 > 5 > 6 > 13 > 14 * 1 +4 > 5 > 7 > 11 * 1 +4 > 5 > 8 > 3 > 11 * 1 +4 > 5 > 11 * 1 +4 > 5 > 14 * 1 +4 > 6 > 1 * 1 +4 > 6 > 1 > 8 * 1 +4 > 6 > 1 > 11 * 1 +4 > 6 > 1 > 11 > 3 > 5 * 1 +4 > 6 > 2 * 1 +4 > 6 > 5 > 2 > 1 > 13 > 12 > 11 > 7 > 3 > 8 > 9 > 10 > 14 * 1 +4 > 6 > 5 > 11 * 1 +4 > 6 > 5 > 11 > 7 > 10 > 12 > 13 > 8 > 9 > 14 > 15 * 1 +4 > 6 > 7 > 5 > 11 * 1 +4 > 6 > 7 > 10 > 14 > 11 > 3 > 5 > 8 > 9 > 12 > 13 > 15 > 1 * 1 +4 > 6 > 8 > 1 * 1 +4 > 6 > 8 > 11 * 1 +4 > 6 > 8 > 11 > 12 > 13 > 3 > 5 > 14 * 1 +4 > 6 > 8 > 11 > 14 * 1 +4 > 6 > 8 > 13 > 15 > 5 > 10 * 1 +4 > 6 > 8 > 14 > 11 * 1 +4 > 6 > 10 * 1 +4 > 6 > 10 > 7 > 11 > 14 > 12 * 1 +4 > 6 > 10 > 11 > 5 > 12 > 8 * 1 +4 > 6 > 10 > 12 * 1 +4 > 6 > 11 > 1 > 3 * 1 +4 > 6 > 11 > 1 > 7 * 1 +4 > 6 > 11 > 1 > 8 > 13 > 2 > 14 * 1 +4 > 6 > 11 > 1 > 13 > 15 > 14 > 7 > 5 > 3 > 2 > 12 > 8 > 9 * 1 +4 > 6 > 11 > 5 > 10 > 12 > 14 * 1 +4 > 6 > 11 > 7 > 10 > 12 > 14 * 1 +4 > 6 > 11 > 8 > 5 * 1 +4 > 6 > 11 > 8 > 7 > 1 * 1 +4 > 6 > 11 > 10 * 1 +4 > 6 > 11 > 10 > 13 > 12 > 14 * 1 +4 > 6 > 11 > 12 > 14 > 10 > 8 > 5 > 13 > 7 > 15 > 3 > 2 > 1 * 1 +4 > 6 > 11 > 13 * 1 +4 > 6 > 11 > 14 * 1 +4 > 6 > 12 * 1 +4 > 6 > 12 > 13 * 1 +4 > 7 > 9 > 15 * 1 +4 > 8 * 1 +4 > 8 > 1 * 1 +4 > 8 > 2 > 7 * 1 +4 > 8 > 3 > 5 > 11 > 14 * 1 +4 > 8 > 3 > 6 > 11 > 9 > 14 > 15 > 2 > 1 > 5 > 13 > 7 > 12 * 1 +4 > 8 > 5 * 1 +4 > 8 > 6 > 1 > 14 > 11 * 1 +4 > 8 > 6 > 5 > 11 > 14 * 1 +4 > 8 > 6 > 11 * 1 +4 > 8 > 11 > 14 > 12 > 13 > 3 * 1 +4 > 8 > 12 * 1 +4 > 8 > 14 > 1 * 1 +4 > 8 > 14 > 3 * 1 +4 > 9 > 12 > 14 * 1 +4 > 9 > 15 > 1 > 3 > 2 > 5 > 7 * 1 +4 > 11 > 3 > 6 * 1 +4 > 11 > 5 > 14 > 6 * 1 +4 > 11 > 6 * 1 +4 > 11 > 6 > 5 > 14 * 1 +4 > 11 > 6 > 10 > 15 * 1 +4 > 11 > 8 * 1 +4 > 11 > 8 > 5 > 6 > 3 > 7 > 14 > 15 > 13 > 12 > 10 > 9 * 1 +4 > 11 > 8 > 6 * 1 +4 > 11 > 8 > 6 > 5 * 1 +4 > 11 > 12 * 1 +4 > 11 > 14 * 1 +4 > 11 > 14 > 8 * 1 +4 > 11 > 15 > 8 > 6 > 5 > 3 > 10 * 1 +4 > 12 > 5 * 1 +4 > 12 > 6 > 1 > 3 > 13 * 1 +4 > 12 > 8 > 5 > 11 * 1 +4 > 13 > 3 * 1 +4 > 13 > 3 > 5 > 7 > 10 > 11 > 14 * 1 +4 > 13 > 5 > 1 > 11 > 3 > 8 * 1 +4 > 14 > 11 > 8 * 1 +5 > 4 > 3 * 1 +5 > 4 > 6 > 11 > 13 > 1 > 3 > 8 * 1 +5 > 4 > 8 > 1 * 1 +5 > 4 > 11 > 15 * 1 +5 > 6 * 1 +5 > 6 > 1 > 7 > 8 > 15 > 13 > 11 > 12 > 4 > 3 > 9 > 14 > 2 * 1 +5 > 6 > 3 > 11 > 12 * 1 +5 > 6 > 4 > 11 > 8 * 1 +5 > 6 > 8 > 14 * 1 +5 > 6 > 11 > 4 > 14 > 8 * 1 +5 > 6 > 11 > 12 * 1 +5 > 6 > 14 > 8 > 7 * 1 +5 > 6 > 14 > 11 > 12 > 8 * 1 +5 > 6 > 15 > 8 > 11 * 1 +5 > 7 > 2 * 1 +5 > 7 > 8 > 11 > 14 > 13 > 4 > 1 > 15 > 12 > 6 > 3 > 9 > 10 * 1 +5 > 7 > 11 * 1 +5 > 8 > 1 * 1 +5 > 8 > 1 > 11 > 12 > 13 > 9 > 2 > 3 > 14 > 7 > 10 > 4 > 6 * 1 +5 > 8 > 6 > 14 * 1 +5 > 8 > 7 > 10 > 3 > 11 * 1 +5 > 8 > 9 * 1 +5 > 8 > 11 * 1 +5 > 8 > 11 > 2 > 6 > 14 * 1 +5 > 8 > 11 > 6 > 12 * 1 +5 > 8 > 11 > 12 * 1 +5 > 8 > 11 > 13 > 6 > 15 > 1 > 7 > 3 * 1 +5 > 8 > 11 > 14 > 7 > 6 * 1 +5 > 8 > 12 > 11 * 1 +5 > 8 > 13 * 1 +5 > 8 > 14 > 1 > 11 * 1 +5 > 9 > 11 * 1 +5 > 11 > 4 > 13 > 6 * 1 +5 > 11 > 6 > 8 > 1 * 1 +5 > 11 > 8 > 4 > 3 * 1 +5 > 11 > 8 > 4 > 6 * 1 +5 > 11 > 8 > 6 > 1 > 7 > 2 > 4 > 3 > 9 > 10 > 14 > 15 > 13 * 1 +5 > 11 > 8 > 14 * 1 +5 > 11 > 15 * 1 +5 > 11 > 15 > 14 > 8 * 1 +5 > 12 > 9 > 2 > 1 > 15 > 8 > 6 > 7 > 13 > 10 > 3 > 14 > 4 * 1 +5 > 14 > 4 * 1 +5 > 14 > 4 > 6 > 15 > 8 > 11 > 12 > 3 * 1 +5 > 14 > 8 * 1 +6 > 1 * 1 +6 > 1 > 3 * 1 +6 > 1 > 7 * 1 +6 > 1 > 7 > 5 * 1 +6 > 1 > 8 > 2 > 5 > 3 > 4 > 7 > 9 > 12 > 15 > 13 > 10 > 11 * 1 +6 > 2 * 1 +6 > 2 > 3 > 5 > 4 > 8 > 1 > 7 > 13 * 1 +6 > 2 > 5 * 1 +6 > 2 > 13 > 15 * 1 +6 > 3 > 4 * 1 +6 > 3 > 5 > 9 > 10 > 4 > 7 > 8 > 11 > 13 > 15 > 14 > 2 > 12 * 1 +6 > 3 > 8 * 1 +6 > 3 > 10 > 4 * 1 +6 > 4 > 1 > 13 * 1 +6 > 4 > 2 > 7 > 11 > 13 > 14 * 1 +6 > 4 > 3 * 1 +6 > 4 > 3 > 1 > 7 > 8 > 5 > 9 > 14 > 12 > 10 > 11 * 1 +6 > 4 > 3 > 1 > 13 * 1 +6 > 4 > 3 > 8 * 1 +6 > 4 > 5 > 11 > 14 > 8 > 15 > 12 * 1 +6 > 4 > 8 > 5 > 11 > 12 > 14 > 15 * 1 +6 > 4 > 8 > 7 > 5 > 3 > 14 > 15 * 1 +6 > 4 > 8 > 11 > 5 * 1 +6 > 4 > 8 > 11 > 7 > 5 > 14 > 10 > 3 * 1 +6 > 4 > 8 > 11 > 12 > 13 > 10 > 5 > 7 > 14 > 15 > 1 > 9 > 2 * 1 +6 > 4 > 8 > 11 > 14 * 1 +6 > 4 > 8 > 12 * 1 +6 > 4 > 10 > 11 > 7 * 1 +6 > 4 > 11 > 3 > 12 > 13 * 1 +6 > 4 > 11 > 5 > 12 > 8 > 10 * 1 +6 > 4 > 11 > 5 > 12 > 14 > 15 > 13 * 1 +6 > 4 > 11 > 7 * 1 +6 > 4 > 11 > 7 > 10 > 12 > 13 > 8 > 9 > 14 * 1 +6 > 4 > 11 > 7 > 10 > 12 > 13 > 8 > 9 > 14 > 15 * 1 +6 > 4 > 11 > 10 > 5 > 12 > 13 > 14 > 15 > 7 > 9 > 8 * 1 +6 > 4 > 11 > 12 > 8 * 1 +6 > 4 > 12 > 11 > 13 * 1 +6 > 4 > 13 > 14 * 1 +6 > 4 > 14 > 11 * 1 +6 > 5 * 1 +6 > 5 > 1 > 4 > 11 > 2 * 1 +6 > 5 > 3 > 4 > 8 > 13 * 1 +6 > 5 > 3 > 8 > 13 > 9 > 14 > 10 > 7 > 1 * 1 +6 > 5 > 4 > 8 > 9 > 10 > 11 * 1 +6 > 5 > 4 > 8 > 11 * 1 +6 > 5 > 4 > 11 > 7 > 3 * 1 +6 > 5 > 4 > 11 > 7 > 10 > 12 > 13 > 8 * 1 +6 > 5 > 4 > 11 > 7 > 10 > 12 > 13 > 14 > 9 > 8 > 15 * 1 +6 > 5 > 4 > 11 > 8 > 12 * 1 +6 > 5 > 4 > 13 * 1 +6 > 5 > 8 * 1 +6 > 5 > 8 > 3 > 13 > 7 > 15 > 14 > 12 > 1 > 11 > 9 > 10 > 4 * 1 +6 > 5 > 8 > 4 * 1 +6 > 5 > 8 > 4 > 11 * 1 +6 > 5 > 8 > 9 > 3 > 2 * 1 +6 > 5 > 8 > 9 > 12 > 11 > 13 * 1 +6 > 5 > 8 > 11 > 14 * 1 +6 > 5 > 8 > 11 > 14 > 15 > 9 > 4 * 1 +6 > 5 > 11 > 2 > 10 > 3 > 7 > 13 > 4 > 1 * 1 +6 > 5 > 11 > 7 > 4 > 10 > 12 > 13 > 8 > 9 > 14 > 15 * 1 +6 > 5 > 11 > 8 > 4 * 1 +6 > 5 > 11 > 12 > 14 > 13 > 4 > 15 * 1 +6 > 7 > 2 > 9 > 10 > 15 > 1 > 3 > 12 > 14 > 13 > 4 > 11 > 5 * 1 +6 > 7 > 2 > 10 > 9 * 1 +6 > 7 > 3 > 4 * 1 +6 > 7 > 4 > 3 > 5 > 2 > 14 > 8 > 1 > 15 * 1 +6 > 7 > 10 > 3 > 12 > 2 > 1 * 1 +6 > 7 > 10 > 12 > 13 > 3 > 4 * 1 +6 > 7 > 11 > 10 > 2 > 13 > 3 > 1 > 9 * 1 +6 > 8 > 1 * 1 +6 > 8 > 4 > 3 > 11 > 14 * 1 +6 > 8 > 4 > 11 * 1 +6 > 8 > 4 > 11 > 13 * 1 +6 > 8 > 4 > 14 > 11 * 1 +6 > 8 > 4 > 14 > 12 > 5 > 11 > 15 > 9 > 7 * 1 +6 > 8 > 9 > 5 > 11 * 1 +6 > 8 > 9 > 10 * 1 +6 > 8 > 11 > 4 > 3 > 13 > 14 > 12 > 15 * 1 +6 > 8 > 12 > 11 * 1 +6 > 8 > 14 > 4 * 1 +6 > 8 > 15 * 1 +6 > 9 > 7 > 13 > 15 > 14 > 8 * 1 +6 > 9 > 10 > 3 > 4 > 1 > 7 > 11 > 13 > 8 * 1 +6 > 10 > 2 * 1 +6 > 10 > 2 > 9 * 1 +6 > 10 > 2 > 11 > 7 > 14 > 12 > 13 * 1 +6 > 10 > 14 > 8 * 1 +6 > 11 > 1 * 1 +6 > 11 > 1 > 3 > 4 > 8 > 15 * 1 +6 > 11 > 2 > 1 > 13 > 3 * 1 +6 > 11 > 2 > 13 > 15 > 8 * 1 +6 > 11 > 3 > 12 > 7 * 1 +6 > 11 > 4 * 1 +6 > 11 > 4 > 10 > 14 > 5 * 1 +6 > 11 > 4 > 14 > 12 * 1 +6 > 11 > 5 * 1 +6 > 11 > 5 > 3 > 14 * 1 +6 > 11 > 5 > 15 * 1 +6 > 11 > 8 > 12 > 5 > 4 > 1 > 13 > 14 * 1 +6 > 11 > 13 > 4 > 14 > 8 * 1 +6 > 12 > 8 * 1 +6 > 12 > 8 > 3 * 1 +6 > 12 > 8 > 4 > 11 > 14 * 1 +6 > 13 * 1 +6 > 13 > 4 > 3 > 5 * 1 +6 > 13 > 5 * 1 +6 > 13 > 12 > 11 * 1 +6 > 13 > 12 > 11 > 9 > 15 * 1 +6 > 14 * 1 +6 > 14 > 8 * 1 +6 > 14 > 9 * 1 +6 > 14 > 11 * 1 +6 > 14 > 11 > 1 * 1 +6 > 14 > 12 > 10 > 8 > 9 > 11 > 1 > 4 > 13 * 1 +7 > 1 > 2 * 1 +7 > 1 > 2 > 3 > 10 > 9 * 1 +7 > 1 > 2 > 8 > 3 * 1 +7 > 1 > 3 > 2 > 6 > 13 > 15 * 1 +7 > 1 > 3 > 15 > 13 > 6 > 2 * 1 +7 > 1 > 4 * 1 +7 > 2 * 1 +7 > 2 > 3 > 1 > 13 > 15 * 1 +7 > 2 > 6 * 1 +7 > 2 > 8 > 10 * 1 +7 > 2 > 9 * 1 +7 > 2 > 13 * 1 +7 > 4 > 2 * 1 +7 > 4 > 3 > 1 > 6 > 13 > 14 > 15 > 5 > 2 > 8 > 9 > 10 > 12 * 1 +7 > 4 > 6 > 10 > 15 > 14 > 11 > 13 > 8 > 3 > 12 > 1 > 5 > 2 * 1 +7 > 4 > 8 > 6 > 5 * 1 +7 > 5 > 8 * 1 +7 > 5 > 12 * 1 +7 > 6 * 1 +7 > 6 > 1 * 1 +7 > 6 > 2 > 5 > 4 > 8 > 14 * 1 +7 > 6 > 10 > 11 > 1 * 1 +7 > 6 > 12 > 8 * 1 +7 > 8 > 2 > 13 > 6 > 3 * 1 +7 > 8 > 5 * 1 +7 > 8 > 12 * 1 +7 > 8 > 15 * 1 +7 > 9 > 10 * 1 +7 > 10 > 4 > 2 > 13 > 3 * 1 +7 > 10 > 6 > 4 * 1 +7 > 10 > 8 * 1 +7 > 11 > 12 > 3 > 4 > 6 > 5 > 8 * 1 +7 > 11 > 14 * 1 +7 > 13 > 6 * 1 +7 > 15 > 1 > 3 > 13 * 1 +7 > 15 > 3 * 1 +7 > 15 > 3 > 1 > 2 * 1 +7 > 15 > 5 * 1 +8 > 1 * 1 +8 > 1 > 3 * 1 +8 > 1 > 4 * 1 +8 > 1 > 6 * 1 +8 > 2 > 3 > 6 * 1 +8 > 2 > 7 > 10 > 9 * 1 +8 > 3 > 7 * 1 +8 > 3 > 10 > 13 > 1 * 1 +8 > 3 > 12 * 1 +8 > 3 > 13 > 10 * 1 +8 > 4 * 1 +8 > 4 > 3 > 6 > 1 > 2 > 5 > 7 > 13 > 11 > 10 > 15 > 14 > 9 * 1 +8 > 4 > 5 * 1 +8 > 4 > 5 > 11 > 14 * 1 +8 > 4 > 6 > 14 > 15 * 1 +8 > 4 > 11 * 1 +8 > 4 > 11 > 5 > 6 > 12 > 14 > 15 > 9 * 1 +8 > 4 > 12 > 14 > 15 > 3 > 6 > 5 > 1 > 2 > 7 > 9 > 10 > 11 * 1 +8 > 5 > 2 > 6 > 7 > 9 > 10 * 1 +8 > 5 > 3 > 4 > 9 > 11 > 14 * 1 +8 > 5 > 4 * 1 +8 > 5 > 4 > 6 > 1 > 11 > 14 > 7 > 10 > 3 > 9 > 15 * 1 +8 > 5 > 4 > 11 > 6 * 1 +8 > 5 > 6 * 1 +8 > 5 > 6 > 4 > 11 > 12 > 10 > 13 * 1 +8 > 5 > 9 > 3 > 11 * 1 +8 > 5 > 9 > 6 > 4 > 14 * 1 +8 > 5 > 11 > 4 > 12 > 14 * 1 +8 > 5 > 11 > 9 > 6 * 1 +8 > 5 > 11 > 12 > 6 > 4 * 1 +8 > 5 > 11 > 13 > 6 > 1 * 1 +8 > 5 > 11 > 14 * 1 +8 > 5 > 12 * 1 +8 > 5 > 12 > 4 > 6 * 1 +8 > 5 > 14 * 1 +8 > 6 > 1 * 1 +8 > 6 > 2 * 1 +8 > 6 > 3 > 11 > 14 * 1 +8 > 6 > 4 * 1 +8 > 6 > 4 > 3 * 1 +8 > 6 > 4 > 11 > 5 > 1 > 15 > 14 > 13 > 12 > 10 > 9 > 2 > 3 * 1 +8 > 6 > 5 > 9 * 1 +8 > 6 > 5 > 14 * 1 +8 > 6 > 5 > 14 > 11 > 12 > 15 * 1 +8 > 6 > 11 > 9 * 1 +8 > 6 > 11 > 14 * 1 +8 > 6 > 14 > 3 > 4 * 1 +8 > 6 > 14 > 12 * 1 +8 > 6 > 15 * 1 +8 > 7 > 1 * 1 +8 > 7 > 2 > 1 > 15 > 11 * 1 +8 > 7 > 6 * 1 +8 > 7 > 11 * 1 +8 > 9 > 5 * 1 +8 > 9 > 5 > 11 * 1 +8 > 9 > 11 * 1 +8 > 9 > 11 > 5 * 1 +8 > 9 > 11 > 5 > 6 * 1 +8 > 10 > 12 * 1 +8 > 11 > 2 * 1 +8 > 11 > 3 > 14 * 1 +8 > 11 > 4 * 1 +8 > 11 > 4 > 14 > 5 > 9 > 3 * 1 +8 > 11 > 5 > 4 > 6 > 9 > 14 > 15 * 1 +8 > 11 > 5 > 6 * 1 +8 > 11 > 5 > 9 > 6 * 1 +8 > 11 > 6 * 1 +8 > 11 > 6 > 3 > 4 > 14 * 1 +8 > 11 > 6 > 5 > 14 > 3 > 15 > 12 * 1 +8 > 11 > 6 > 12 * 1 +8 > 11 > 6 > 14 * 1 +8 > 11 > 10 * 1 +8 > 11 > 10 > 4 * 1 +8 > 11 > 14 > 12 * 1 +8 > 12 > 3 * 1 +8 > 12 > 6 * 1 +8 > 12 > 15 > 6 > 14 * 1 +8 > 13 > 15 * 1 +8 > 14 > 4 * 1 +8 > 14 > 10 * 1 +8 > 14 > 11 * 1 +8 > 14 > 13 > 6 * 1 +9 > 6 > 12 * 1 +9 > 6 > 12 > 14 > 8 * 1 +9 > 8 > 6 > 4 * 1 +9 > 8 > 11 > 13 > 3 > 4 > 10 * 1 +9 > 8 > 11 > 14 > 3 > 4 * 1 +9 > 8 > 11 > 14 > 3 > 4 > 10 * 1 +9 > 8 > 12 * 1 +9 > 10 > 2 * 1 +9 > 10 > 8 > 2 * 1 +9 > 12 > 6 * 1 +9 > 12 > 6 > 8 > 2 * 1 +9 > 14 > 8 * 1 +10 > 2 > 6 * 1 +10 > 2 > 7 > 9 * 1 +10 > 5 > 6 * 1 +10 > 6 > 4 > 11 > 12 * 1 +10 > 7 > 2 * 1 +10 > 8 > 7 * 1 +10 > 9 > 11 * 1 +10 > 11 > 14 * 1 +10 > 14 > 4 > 5 > 7 > 6 > 8 > 11 * 1 +10 > 14 > 6 * 1 +10 > 14 > 6 > 11 > 4 * 1 +10 > 14 > 7 > 6 > 4 > 12 > 11 * 1 +10 > 14 > 15 > 11 > 9 > 4 > 5 > 6 > 7 > 12 > 13 * 1 +11 > 1 > 4 > 2 * 1 +11 > 1 > 4 > 7 > 5 > 8 > 9 > 12 > 10 > 3 > 2 > 14 > 15 > 6 * 1 +11 > 2 > 6 * 1 +11 > 3 > 5 * 1 +11 > 3 > 8 * 1 +11 > 4 > 1 * 1 +11 > 4 > 5 > 6 > 14 > 3 > 12 * 1 +11 > 4 > 5 > 12 * 1 +11 > 4 > 6 > 1 > 7 > 10 > 5 > 14 * 1 +11 > 4 > 6 > 3 > 14 * 1 +11 > 4 > 6 > 8 * 1 +11 > 4 > 13 > 14 > 12 * 1 +11 > 4 > 14 > 6 * 1 +11 > 5 * 1 +11 > 5 > 4 > 6 * 1 +11 > 5 > 6 > 4 > 8 > 3 > 13 > 14 * 1 +11 > 5 > 6 > 8 * 1 +11 > 5 > 8 * 1 +11 > 5 > 8 > 3 > 4 > 14 * 1 +11 > 5 > 12 > 14 * 1 +11 > 5 > 14 > 4 * 1 +11 > 6 * 1 +11 > 6 > 4 > 3 * 1 +11 > 6 > 4 > 8 * 1 +11 > 6 > 5 > 14 * 1 +11 > 6 > 7 > 14 * 1 +11 > 6 > 8 * 1 +11 > 6 > 8 > 3 > 4 > 5 > 13 > 14 * 1 +11 > 6 > 8 > 3 > 4 > 13 > 14 > 15 > 12 > 7 > 2 > 1 > 9 > 10 * 1 +11 > 6 > 10 * 1 +11 > 6 > 12 > 14 > 1 > 3 > 4 > 7 > 13 * 1 +11 > 6 > 13 > 12 > 8 > 14 > 1 * 1 +11 > 6 > 14 * 1 +11 > 7 > 15 * 1 +11 > 8 > 2 * 1 +11 > 8 > 3 > 5 > 4 > 6 * 1 +11 > 8 > 4 > 3 > 5 * 1 +11 > 8 > 5 * 1 +11 > 8 > 5 > 6 * 1 +11 > 8 > 5 > 6 > 14 > 12 > 1 > 4 * 1 +11 > 8 > 6 > 10 > 12 * 1 +11 > 8 > 10 * 1 +11 > 8 > 12 * 1 +11 > 8 > 15 > 14 > 3 > 6 * 1 +11 > 9 * 1 +11 > 9 > 7 > 4 > 3 > 14 > 12 * 1 +11 > 10 > 7 > 2 > 6 > 14 > 15 > 12 > 13 > 9 > 1 > 8 > 5 > 3 * 1 +11 > 12 > 5 > 6 > 15 * 1 +11 > 12 > 6 * 1 +11 > 12 > 8 > 6 * 1 +11 > 13 > 12 > 2 > 14 * 1 +11 > 13 > 12 > 6 > 14 * 1 +11 > 14 * 1 +11 > 14 > 4 * 1 +11 > 14 > 5 * 1 +11 > 14 > 6 > 5 > 8 * 1 +11 > 14 > 6 > 5 > 8 > 4 * 1 +11 > 14 > 6 > 12 * 1 +11 > 14 > 8 * 1 +11 > 14 > 8 > 6 > 3 > 10 > 12 * 1 +11 > 14 > 9 * 1 +11 > 15 > 6 * 1 +11 > 15 > 14 > 12 > 8 > 6 * 1 +12 > 2 > 6 * 1 +12 > 3 > 4 > 6 > 13 * 1 +12 > 3 > 8 * 1 +12 > 4 > 13 * 1 +12 > 5 > 8 > 4 * 1 +12 > 6 * 1 +12 > 6 > 1 * 1 +12 > 6 > 1 > 8 * 1 +12 > 6 > 3 * 1 +12 > 6 > 5 > 4 > 11 > 7 > 10 > 13 > 8 > 9 > 14 > 15 * 1 +12 > 6 > 8 * 1 +12 > 8 > 4 * 1 +12 > 8 > 6 * 1 +12 > 8 > 6 > 11 > 5 * 1 +12 > 8 > 13 * 1 +12 > 9 * 1 +12 > 9 > 8 * 1 +12 > 11 > 5 * 1 +12 > 11 > 8 > 6 > 4 * 1 +12 > 14 > 6 * 1 +13 > 1 > 3 > 7 > 15 > 2 * 1 +13 > 1 > 6 * 1 +13 > 3 > 7 > 15 > 1 > 9 * 1 +13 > 6 * 1 +13 > 6 > 3 * 1 +13 > 7 > 6 * 1 +13 > 8 * 1 +13 > 8 > 4 > 6 > 1 * 1 +13 > 11 > 1 > 6 > 8 > 5 > 10 * 1 +13 > 11 > 6 > 5 * 1 +13 > 11 > 6 > 15 * 1 +13 > 12 > 4 * 1 +14 > 1 > 11 > 6 > 8 > 10 * 1 +14 > 3 > 8 > 10 > 13 * 1 +14 > 4 * 1 +14 > 4 > 5 * 1 +14 > 5 > 11 * 1 +14 > 6 * 1 +14 > 6 > 4 > 3 * 1 +14 > 6 > 4 > 11 > 10 > 5 > 8 > 1 * 1 +14 > 6 > 7 * 1 +14 > 6 > 8 > 5 > 4 > 11 > 3 > 12 * 1 +14 > 6 > 8 > 11 > 4 > 3 * 1 +14 > 6 > 11 * 1 +14 > 6 > 11 > 15 > 8 > 7 > 4 > 12 > 5 > 13 > 9 * 1 +14 > 8 > 6 > 2 * 1 +14 > 8 > 10 * 1 +14 > 8 > 10 > 6 * 1 +14 > 8 > 11 > 3 > 4 > 6 * 1 +14 > 10 > 8 * 1 +14 > 10 > 11 * 1 +14 > 11 > 4 * 1 +14 > 11 > 4 > 5 > 8 * 1 +14 > 11 > 4 > 5 > 13 * 1 +14 > 11 > 4 > 7 > 2 * 1 +14 > 11 > 8 > 12 * 1 +14 > 11 > 12 * 1 +14 > 12 * 1 +14 > 12 > 8 > 6 > 1 > 10 * 1 +14 > 12 > 8 > 11 > 10 * 1 +14 > 12 > 15 > 4 > 8 > 7 > 13 > 10 > 1 > 3 > 6 > 5 > 2 > 11 * 1 +14 > 15 > 6 > 11 > 10 > 5 * 1 +15 > 3 > 2 > 1 * 1 +15 > 3 > 13 > 7 > 10 * 1 +15 > 6 > 7 * 1 +15 > 7 * 1 +15 > 7 > 2 * 1 +15 > 7 > 2 > 11 > 10 * 1 +15 > 7 > 14 * 1 +15 > 8 > 6 * 1 +15 > 11 * 1 +15 > 11 > 4 > 2 > 6 > 8 > 14 > 13 > 12 > 1 > 3 > 5 > 7 > 10 * 1 +15 > 12 > 6 * 1 +15 > 12 > 11 * 1 +15 > 14 * 1 +15 > 14 > 11 > 3 > 4 * 1 \ No newline at end of file diff --git a/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/JeffersonTest.php b/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/JeffersonTest.php new file mode 100644 index 0000000..7191374 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/JeffersonTest.php @@ -0,0 +1,47 @@ +election = new Election; + } + + # https://fr.wikipedia.org/wiki/Scrutin_proportionnel_plurinominal#M%C3%A9thode_de_Jefferson_ou_m%C3%A9thode_D'Hondt + public function testResult_1(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->setNumberOfSeats(6); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes('A * 42; B ^31; C *15; D ^12'); // Mix weight and number + + self::assertSame(['A' =>3, 'B' => 2, 'C' => 1, 'D' => 0], $this->election->getResult('Jefferson')->getStats()['Seats per Candidates']); + } + + public function testResult_Tideman_A03(): void + { + $cef = new CondorcetElectionFormat(__DIR__.'/'.'A03.cvotes'); + $cef->setDataToAnElection($this->election); + + $this->election->setImplicitRanking(false); // Empty ranking was throw an error. + + $this->election->getResult('Jefferson'); + + self::assertTrue(true); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/SainteLagueTest.php b/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/SainteLagueTest.php new file mode 100644 index 0000000..f84e009 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/HighestAverages/SainteLagueTest.php @@ -0,0 +1,161 @@ +election = new Election; + } + + protected function tearDown(): void + { + $this->election->setMethodOption('SainteLague', 'FirstDivisor', 1); + } + + # https://fr.wikipedia.org/wiki/Scrutin_proportionnel_plurinominal#M%C3%A9thode_de_Sainte-Lagu%C3%AB + public function testResult_1(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->setNumberOfSeats(7); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes('A ^53; B ^24; C ^23'); + + self::assertSame('A > B > C > A > A > B > C', $this->election->getResult('SainteLague')->getResultAsString()); + + self::assertSame([ + 'Rounds' => [ + 1 => [ + 'A' => [ + 'Quotient' => 53.0, + 'NumberOfSeatsAllocatedBeforeRound' => 0, + ], + 'B' => [ + 'Quotient' => 24.0, + 'NumberOfSeatsAllocatedBeforeRound' => 0, + ], + 'C' => [ + 'Quotient' => 23.0, + 'NumberOfSeatsAllocatedBeforeRound' => 0, + ], + ], + 2 => [ + 'A' => [ + 'Quotient' => 17.666666666666668, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + 'B' => [ + 'Quotient' => 24.0, + 'NumberOfSeatsAllocatedBeforeRound' => 0, + ], + 'C' => [ + 'Quotient' => 23.0, + 'NumberOfSeatsAllocatedBeforeRound' => 0, + ], + ], + 3 => [ + 'A' => [ + 'Quotient' => 17.666666666666668, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + 'B' => [ + 'Quotient' => 8.0, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + 'C' => [ + 'Quotient' => 23.0, + 'NumberOfSeatsAllocatedBeforeRound' => 0, + ], + ], + 4 => [ + 'A' => [ + 'Quotient' => 17.666666666666668, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + 'B' => [ + 'Quotient' => 8.0, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + 'C' => [ + 'Quotient' => 7.666666666666667, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + ], + 5 => [ + 'A' => [ + 'Quotient' => 10.6, + 'NumberOfSeatsAllocatedBeforeRound' => 2, + ], + 'B' => [ + 'Quotient' => 8.0, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + 'C' => [ + 'Quotient' => 7.666666666666667, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + ], + 6 => [ + 'A' => [ + 'Quotient' => 7.571428571428571, + 'NumberOfSeatsAllocatedBeforeRound' => 3, + ], + 'B' => [ + 'Quotient' => 8.0, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + 'C' => [ + 'Quotient' => 7.666666666666667, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + ], + 7 => [ + 'A' => [ + 'Quotient' => 7.571428571428571, + 'NumberOfSeatsAllocatedBeforeRound' => 3, + ], + 'B' => [ + 'Quotient' => 4.8, + 'NumberOfSeatsAllocatedBeforeRound' => 2, + ], + 'C' => [ + 'Quotient' => 7.666666666666667, + 'NumberOfSeatsAllocatedBeforeRound' => 1, + ], + ], + ], + 'Seats per Candidates' => [ + 'A' => 3, + 'B' => 2, + 'C' => 2, + ], + ], $this->election->getResult('SainteLague')->getStats()); + } + + # https://www.regjeringen.no/no/tema/valg-og-demokrati/den-norske-valgordningen/valgordningen/id456636/ + public function testNorwegianVariant_1(): void + { + $this->election->setMethodOption('SainteLague', 'FirstDivisor', 1.4); + + $this->election->parseCandidates('H;Ap;FrP;SV;SP;KrF'); + + $this->election->setNumberOfSeats(11); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes('H ^81140; Ap ^80862; FrP ^39851; SV ^26295; SP ^12187; KrF ^11229'); + + self::assertSame('H > Ap > FrP > H > Ap > SV > H > Ap > FrP > H > Ap', $this->election->getResult('SainteLague')->getResultAsString()); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/HighestAveragesAndLargestRemainderMethodsTest.php b/include/Condorcet/Tests/src/Algo/Methods/HighestAveragesAndLargestRemainderMethodsTest.php new file mode 100644 index 0000000..db3585e --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/HighestAveragesAndLargestRemainderMethodsTest.php @@ -0,0 +1,259 @@ +election = new Election; + } + + protected function tearDown(): void + { + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::HARE); + } + + public function testFranceLegislatives2022_1erTour(): void + { + $this->election->setNumberOfSeats(577); + $this->election->allowsVoteWeight(true); + + $this->election->parseCandidates( + 'Divers extrême gauche + Parti radical de gauche + Nouvelle union populaire écologique et sociale + Divers gauche + Ecologistes + Divers + Régionaliste + Ensemble ! (Majorité présidentielle) + Divers centre + Union des Démocrates et des Indépendants + Les Républicains + Divers droite + Droite souverainiste + Reconquête ! + Rassemblement National + Divers extrême droite' + ); + + $this->election->parseVotes(' + Divers extrême gauche ^266412 + Parti radical de gauche ^126689 + Nouvelle union populaire écologique et sociale ^5836079 + Divers gauche ^713574 + Ecologistes ^608314 + Divers ^192624 + Régionaliste ^291384 + Ensemble ! (Majorité présidentielle) ^5857364 + Divers centre ^283612 + Union des Démocrates et des Indépendants ^198062 + Les Républicains ^2370440 + Divers droite ^530782 + Droite souverainiste ^249603 + Reconquête ! ^964775 + Rassemblement National ^4248537 + Divers extrême droite ^6457 + '); + + // SainteLeague + self::assertSame([ + 'Divers extrême gauche' => 7, + 'Parti radical de gauche' => 3, + 'Nouvelle union populaire écologique et sociale' => 148, + 'Divers gauche' => 18, + 'Ecologistes' => 15, + 'Divers' => 5, + 'Régionaliste' => 7, + 'Ensemble ! (Majorité présidentielle)' => 149, + 'Divers centre' => 7, + 'Union des Démocrates et des Indépendants' => 5, + 'Les Républicains' => 60, + 'Divers droite' => 14, + 'Droite souverainiste' => 6, + 'Reconquête !' => 25, + 'Rassemblement National' => 108, + 'Divers extrême droite' => 0, + ], $this->election->getResult('SainteLague')->getStats()['Seats per Candidates']); + + $this->assertSame(577, array_sum($this->election->getResult('SainteLague')->getStats()['Seats per Candidates'])); + $this->assertCount(577, $this->election->getResult('SainteLague')->getResultAsArray()); + + // Jefferson + self::assertSame([ + 'Divers extrême gauche' => 6, + 'Parti radical de gauche' => 3, + 'Nouvelle union populaire écologique et sociale' => 150, + 'Divers gauche' => 18, + 'Ecologistes' => 15, + 'Divers' => 4, + 'Régionaliste' => 7, + 'Ensemble ! (Majorité présidentielle)' => 150, + 'Divers centre' => 7, + 'Union des Démocrates et des Indépendants' => 5, + 'Les Républicains' => 60, + 'Divers droite' => 13, + 'Droite souverainiste' => 6, + 'Reconquête !' => 24, + 'Rassemblement National' => 109, + 'Divers extrême droite' => 0, + ], $this->election->getResult('Jefferson')->getStats()['Seats per Candidates']); + + $this->assertSame(577, array_sum($this->election->getResult('Jefferson')->getStats()['Seats per Candidates'])); + $this->assertCount(577, $this->election->getResult('Jefferson')->getResultAsArray()); + + // Hare-LR + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::HARE); // Hare-LR + self::assertSame([ + 'Divers extrême gauche' => 7, + 'Parti radical de gauche' => 3, + 'Nouvelle union populaire écologique et sociale' => 148, + 'Divers gauche' => 18, + 'Ecologistes' => 15, + 'Divers' => 5, + 'Régionaliste' => 7, + 'Ensemble ! (Majorité présidentielle)' => 149, + 'Divers centre' => 7, + 'Union des Démocrates et des Indépendants' => 5, + 'Les Républicains' => 60, + 'Divers droite' => 14, + 'Droite souverainiste' => 6, + 'Reconquête !' => 25, + 'Rassemblement National' => 108, + 'Divers extrême droite' => 0, + ], $this->election->getResult('LargestRemainder')->getStats()['Seats per Candidates']); + + $this->assertSame(577, array_sum($this->election->getResult('LargestRemainder')->getStats()['Seats per Candidates'])); + $this->assertCount(577, $this->election->getResult('LargestRemainder')->getResultAsArray()); + + // Droop-LR + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::DROOP); // Droop-LR + self::assertSame([ + 'Divers extrême gauche' => 7, + 'Parti radical de gauche' => 3, + 'Nouvelle union populaire écologique et sociale' => 148, + 'Divers gauche' => 18, + 'Ecologistes' => 15, + 'Divers' => 5, + 'Régionaliste' => 7, + 'Ensemble ! (Majorité présidentielle)' => 149, + 'Divers centre' => 7, + 'Union des Démocrates et des Indépendants' => 5, + 'Les Républicains' => 60, + 'Divers droite' => 14, + 'Droite souverainiste' => 6, + 'Reconquête !' => 25, + 'Rassemblement National' => 108, + 'Divers extrême droite' => 0, + ], $this->election->getResult('LargestRemainder')->getStats()['Seats per Candidates']); + + $this->assertSame(577, array_sum($this->election->getResult('LargestRemainder')->getStats()['Seats per Candidates'])); + $this->assertCount(577, $this->election->getResult('LargestRemainder')->getResultAsArray()); + + // Hagenbach-Bischoff-LR + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::HAGENBACH_BISCHOFF); // Hagenbach-Bischoff-LR + self::assertSame([ + 'Divers extrême gauche' => 7, + 'Parti radical de gauche' => 3, + 'Nouvelle union populaire écologique et sociale' => 148, + 'Divers gauche' => 18, + 'Ecologistes' => 15, + 'Divers' => 5, + 'Régionaliste' => 7, + 'Ensemble ! (Majorité présidentielle)' => 149, + 'Divers centre' => 7, + 'Union des Démocrates et des Indépendants' => 5, + 'Les Républicains' => 60, + 'Divers droite' => 14, + 'Droite souverainiste' => 6, + 'Reconquête !' => 25, + 'Rassemblement National' => 108, + 'Divers extrême droite' => 0, + ], $this->election->getResult('LargestRemainder')->getStats()['Seats per Candidates']); + + $this->assertSame(577, array_sum($this->election->getResult('LargestRemainder')->getStats()['Seats per Candidates'])); + $this->assertCount(577, $this->election->getResult('LargestRemainder')->getResultAsArray()); + + // Imperiali-LR + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::IMPERIALI); // Imperiali-LR + self::assertSame([ + 'Divers extrême gauche' => 7, + 'Parti radical de gauche' => 3, + 'Nouvelle union populaire écologique et sociale' => 149, + 'Divers gauche' => 18, + 'Ecologistes' => 15, + 'Divers' => 5, + 'Régionaliste' => 7, + 'Ensemble ! (Majorité présidentielle)' => 149, + 'Divers centre' => 7, + 'Union des Démocrates et des Indépendants' => 5, + 'Les Républicains' => 60, + 'Divers droite' => 13, + 'Droite souverainiste' => 6, + 'Reconquête !' => 25, + 'Rassemblement National' => 108, + 'Divers extrême droite' => 0, + ], $this->election->getResult('LargestRemainder')->getStats()['Seats per Candidates']); + + $this->assertSame(577, array_sum($this->election->getResult('LargestRemainder')->getStats()['Seats per Candidates'])); + $this->assertCount(577, $this->election->getResult('LargestRemainder')->getResultAsArray()); + } + + # https://www.electoral-reform.org.uk/what-is-the-difference-between-dhondt-sainte-lague-and-hare/ + public function testResult_1(): void + { + $this->election->parseCandidates('Con;Lab;LD;Brexit;Ash Ind;Green;Others'); + $this->election->setNumberOfSeats(11); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes('Con ^258794; Lab ^204011; LD ^33604; Brexit ^15728; Ash Ind ^13498; Green ^10375; Others ^9743'); + + self::assertSame('Con > Lab > Con > Lab > Con > Lab > Con > LD > Lab > Con > Con', $this->election->getResult('SainteLague')->getResultAsString()); + self::assertSame('Con > Lab > Con > Lab > Con > Lab > Con > Con > Lab > Con > Lab', $this->election->getResult('Jefferson')->getResultAsString()); + + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::HARE); // Hare-LR + self::assertSame('Con > Con > Lab > Con > Lab > Con > Lab > Con > Lab > LD > Brexit', $this->election->getResult('LargestRemainder')->getResultAsString()); + } + + # https://en.wikipedia.org/wiki/Webster/Sainte-Lagu%C3%AB_method + # https://en.wikipedia.org/wiki/D%27Hondt_method + public function testResult_2(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->setNumberOfSeats(8); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes('A ^100000; B ^80000; C ^30000; D ^20000'); + + self::assertSame('A > B > A > C > B > A > D > B', $this->election->getResult('SainteLague')->getResultAsString()); + self::assertSame('A > B > A > B > A > C > B > A', $this->election->getResult('Jefferson')->getResultAsString()); + } + + public function testTiesOnFirstRank(): void + { + $this->election->setNumberOfSeats(1); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->addVote('A = B > C'); + self::assertSame([], $this->election->getResult('SainteLague')->getResultAsArray()); + + $this->election->addVote('B>A'); + self::assertSame('B', $this->election->getResult('SainteLague')->getResultAsString()); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/InstantRunoff/InstantRunoffTest.php b/include/Condorcet/Tests/src/Algo/Methods/InstantRunoff/InstantRunoffTest.php new file mode 100644 index 0000000..166bf88 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/InstantRunoff/InstantRunoffTest.php @@ -0,0 +1,185 @@ +election = new Election; + } + + public function testResult_1(): void + { + # From https://fr.wikipedia.org/wiki/Vote_alternatif + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D * 42 + B>C>D>A * 26 + C>D>B>A * 15 + D>C>B>A * 17 + '); + + self::assertSame( + [ + 1 => 'D', + 2 => 'A', + 3 => 'B', + 4 => 'C', ], + $this->election->getResult('InstantRunoff')->getResultAsArray(true) + ); + + self::assertSame( + [ + 'majority' => 50.0, + 'rounds' => [ + 1 => [ + 'A' => 42, + 'B' => 26, + 'C' => 15, + 'D' => 17, + ], + 2 => [ + 'A' => 42, + 'B' => 26, + 'D' => 32, + ], + 3 => [ + 'A' => 42, + 'D' => 58, + ], + ], + ], + $this->election->getResult('InstantRunoff')->getStats() + ); + } + + public function testResult_2(): void + { + # From https://en.wikipedia.org/wiki/Instant-runoff_voting#Examples + + $this->election->addCandidate('bob'); + $this->election->addCandidate('sue'); + $this->election->addCandidate('bill'); + + $this->election->parseVotes(' + bob > bill > sue + sue > bob > bill + bill > sue > bob + bob > bill > sue + sue > bob > bill + '); + + self::assertSame( + [ + 1 => 'sue', + 2 => 'bob', + 3 => 'bill', ], + $this->election->getResult('InstantRunoff')->getResultAsArray(true) + ); + } + + public function testResult_3(): void + { + $this->election->addCandidate('bob'); + $this->election->addCandidate('sue'); + $this->election->addCandidate('bill'); + + $this->election->parseVotes(' + bob > bill > sue + sue > bob > bill + bill > sue > bob + bob > bill > sue + sue > bob > bill + bill > bob > sue + '); + + self::assertSame( + [ + 1 => 'bob', + 2 => 'bill', + 3 => 'sue', ], + $this->election->getResult('InstantRunoff')->getResultAsArray(true) + ); + } + + public function testResult_4(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A=B=C + '); + + self::assertSame( + [ + 1 => ['A', 'B', 'C'], ], + $this->election->getResult('InstantRunoff')->getResultAsArray(true) + ); + } + + public function testResult_Equality(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + + $this->election->parseVotes(' + A + B + '); + + self::assertSame( + [ + 1 => ['A', 'B'], ], + $this->election->getResult('InstantRunoff')->getResultAsArray(true) + ); + } + + public function testResult_TieBreaking(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A * 4 + B * 4 + C>A * 2 + D>C>B * 2 + '); + + self::assertSame( + [ + 1 => ['A', 'B'], + 3 => 'C', + 4 => 'D', + ], + $this->election->getResult('InstantRunoff')->getResultAsArray(true) + ); + } + + public function testInfiniteLoopOnTidemanDataset3IfExplicitRanking(): void + { + $election = (new DavidHillFormat(__DIR__.'/../../../Tools/Converters/TidemanData/A3.HIL'))->setDataToAnElection(); + + $election->setImplicitRanking(false); + + self::assertSame('6 > 8 > 4 > 11 > 2 > 5 > 14 > 1 = 7 > 12 > 3 > 9 > 10 > 15 > 13', $election->getResult('InstantRunoff')->getResultAsString()); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/KemenyYoung/KemenyYoungTest.php b/include/Condorcet/Tests/src/Algo/Methods/KemenyYoung/KemenyYoungTest.php new file mode 100644 index 0000000..61e2627 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/KemenyYoung/KemenyYoungTest.php @@ -0,0 +1,205 @@ +election = new Election; + } + + public function testResult_1(): void + { + $this->election->addCandidate('Memphis'); + $this->election->addCandidate('Nashville'); + $this->election->addCandidate('Knoxville'); + $this->election->addCandidate('Chattanooga'); + + $this->election->parseVotes(' + Memphis > Nashville > Chattanooga * 42 + Nashville > Chattanooga > Knoxville * 26 + Chattanooga > Knoxville > Nashville * 15 + Knoxville > Chattanooga > Nashville * 17 + '); + + + self::assertSame( + [ + 1 => 'Nashville', + 2 => 'Chattanooga', + 3 => 'Knoxville', + 4 => 'Memphis', + ], + $this->election->getResult('KemenyYoung')->getResultAsArray(true) + ); + + self::assertSame(393, $this->election->getResult('KemenyYoung')->getStats()['Best Score']); + + self::assertSame($this->election->getWinner(), $this->election->getWinner('KemenyYoung')); + } + + /** + * @preserveGlobalState disabled + */ + public function testResult2(): void + { + $this->election->parseCandidates('Elliot;Roland;Meredith;Selden'); + + $this->election->parseVotes(' + Elliot > Roland ^30 + Elliot > Meredith ^60 + Elliot > Selden ^60 + Roland > Meredith ^70 + Roland > Selden ^60 + Meredith > Selden ^40 + '); + + $this->election->setImplicitRanking(false); + + self::assertSame( + [ + 1 => 'Elliot', + 2 => 'Roland', + 3 => 'Meredith', + 4 => 'Selden', + ], + $this->election->getResult('KemenyYoung')->getResultAsArray(true) + ); + } + + public function testStats_1(): void + { + $this->election->setStatsVerbosity(StatsVerbosity::FULL); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + + $this->election->parseVotes($r = 'A > B'); + + self::assertSame( + $r, + $this->election->getResult('KemenyYoung')->getResultAsString() + ); + + self::assertSame( + [ + 'Best Score' => 1, + 'Ranking In Conflicts' => 0, + 'Ranking Scores' => [ + [1 => 'A', 2 => 'B', 'score' => 1], + [1 => 'B', 2 => 'A', 'score' => 0], + ], + ], + $this->election->getResult('KemenyYoung')->getStats() + ); + + $this->election->setStatsVerbosity(StatsVerbosity::STD); + + self::assertArrayNotHasKey('rankingScores', $this->election->getResult('KemenyYoung')->getStats()); + } + + public function testMaxCandidates(): never + { + $this->expectException(CandidatesMaxNumberReachedException::class); + $this->expectExceptionMessage("Maximum number of candidates reached: The method 'Kemeny–Young' is configured to accept only ".KemenyYoung::$MaxCandidates.' candidates'); + + for ($i=0; $i < (KemenyYoung::$MaxCandidates + 1); $i++) { + $this->election->addCandidate(); + } + + $this->election->parseVotes('A'); + + $this->election->getWinner('KemenyYoung'); + } + + public function testConflicts(): void + { + $this->election->parseCandidates('A;B;C'); + + $this->election->parseVotes(' + A>B>C; + B>C>A; + C>A>B'); + + $result = $this->election->getResult('KemenyYoung'); + + self::assertEquals( + [0 => [ + 'type' => 42, + 'msg' => '3;5', + ], + ], + $result->getWarning(\CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung::CONFLICT_WARNING_CODE) + ); + + self::assertEquals( + [0 => [ + 'type' => 42, + 'msg' => '3;5', + ], + ], + $result->getWarning() + ); + + self::assertSame(3, $result->getStats()['Ranking In Conflicts']); + + $this->election->addVote('A>B>C'); + + $result = $this->election->getResult('KemenyYoung'); + + self::assertEquals( + [], + $result->getWarning(\CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung::CONFLICT_WARNING_CODE) + ); + + self::assertEquals('A', $this->election->getWinner('KemenyYoung')); + } + + public function testKemenyWithOnly1Candidate(): void + { + $candidate[] = $this->election->addCandidate(); + + $this->election->addVote($candidate); + + self::assertSame($candidate[0], $this->election->getWinner('KemenyYoung')); + } + + public function ManyCandidatesProvider(): array + { + return [ + 9 => [9], + 10 => [10], + ]; + } + + /** + * @group large + * @dataProvider ManyCandidatesProvider + */ + public function testKemenyWithManyCandidates(int $candidatesCount): void + { + $original = KemenyYoung::$MaxCandidates; + KemenyYoung::$MaxCandidates = null; + + for ($i=0; $i<$candidatesCount; $i++) { + $candidates[] = $this->election->addCandidate(); + } + + $this->election->addVote($candidates); + + self::assertSame($candidates[0], $this->election->getWinner('KemenyYoung')); + + KemenyYoung::$MaxCandidates = $original; + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/LargestRemainder/LargestRemainderTest.php b/include/Condorcet/Tests/src/Algo/Methods/LargestRemainder/LargestRemainderTest.php new file mode 100644 index 0000000..2f6d389 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/LargestRemainder/LargestRemainderTest.php @@ -0,0 +1,124 @@ +election = new Election; + } + + protected function tearDown(): void + { + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::HARE); + } + + # https://en.wikipedia.org/wiki/Largest_remainder_method + public function testResult_1(): void + { + $this->election->parseCandidates('Yellows;Whites;Reds;Greens;Blues;Pinks'); + + $this->election->setNumberOfSeats(10); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes('Yellows ^47000;Whites ^16000;Reds ^15800;Greens ^12000;Blues ^6100;Pinks ^3100'); + + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::HARE); // Hare-LR + self::assertSame( + [ + 'Yellows' => 5, + 'Whites' => 2, + 'Reds' => 1, + 'Greens' => 1, + 'Blues' => 1, + 'Pinks' => 0, ], + $this->election->getResult('LR')->getStats()['Seats per Candidates'] + ); + + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::DROOP); // Hare-LR + self::assertSame( + [ + 'Yellows' => 5, + 'Whites' => 2, + 'Reds' => 2, + 'Greens' => 1, + 'Blues' => 0, + 'Pinks' => 0, ], + $this->election->getResult('LR')->getStats()['Seats per Candidates'] + ); + } + + # https://en.wikipedia.org/wiki/Largest_remainder_method + public function testResult_2(): void + { + $this->election->parseCandidates('A;B;C;D;E;F'); + + $this->election->setNumberOfSeats(25); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes('A ^1500;B ^1500;C ^900;D^500;E ^500;F ^200'); + + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::HARE); // Hare-LR + self::assertSame( + [ + 'A' => 7, + 'B' => 7, + 'C' => 4, + 'D' => 3, + 'E' => 3, + 'F' => 1, ], + $this->election->getResult('LR')->getStats()['Seats per Candidates'] + ); + } + + # https://en.wikipedia.org/wiki/Largest_remainder_method + public function testResult_3(): void + { + $this->election->parseCandidates('A;B;C;D;E;F'); + + $this->election->setNumberOfSeats(26); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes('A ^1500;B ^1500;C ^900;D^500;E ^500;F ^200'); + + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::HARE); // Hare-LR + self::assertSame( + [ + 'A' => 8, + 'B' => 8, + 'C' => 5, + 'D' => 2, + 'E' => 2, + 'F' => 1, ], + $this->election->getResult('LR')->getStats()['Seats per Candidates'] + ); + } + + // Fixing error with Droop Quotas in some cases + public function testResult_4_LR(): void + { + $this->election->parseCandidates('A;B;C'); + $this->election->setNumberOfSeats(99); + + $this->election->parseVotes('A>B>C;C>B>A;B>A>C'); + + $this->election->setMethodOption('LargestRemainder', 'Quota', StvQuotas::DROOP); // Hare-LR + self::assertSame( + [ + 'A' => 33, + 'B' => 33, + 'C' => 33, + ], + $this->election->getResult('LR')->getStats()['Seats per Candidates'] + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Majority/FirstPastThePostTest.php b/include/Condorcet/Tests/src/Algo/Methods/Majority/FirstPastThePostTest.php new file mode 100644 index 0000000..55ee69f --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Majority/FirstPastThePostTest.php @@ -0,0 +1,203 @@ +election = new Election; + } + + + public function testResult_French2002(): void + { + $this->election->allowsVoteWeight(true); + + $this->election->addCandidate('Chirac'); + $this->election->addCandidate('Jospin'); + $this->election->addCandidate('Le Pen'); + $this->election->addCandidate('Bayrou'); + $this->election->addCandidate('Laguiller'); + $this->election->addCandidate('Chevènement'); + $this->election->addCandidate('Mamère'); + $this->election->addCandidate('Besancenot'); + $this->election->addCandidate('Saint-Josse'); + $this->election->addCandidate('Madelin'); + $this->election->addCandidate('Robert Hue'); + $this->election->addCandidate('Mégret'); + $this->election->addCandidate('Taubira'); + $this->election->addCandidate('Lepage'); + $this->election->addCandidate('Boutin'); + $this->election->addCandidate('Gluckstein'); + + $this->election->parseVotes(' + Chirac > Bayrou = Jospin = Madelin = Chevénement = Mamère = Robert Hue = Taubira = Lepage = Boutin > Saint-Josse ^1988 + Jospin > Chevénement = Taubira = Mamère > Bayrou > Robert Hue > Chirac = Lepage = Boutin > Madelin > Saint-Josse ^ 1618 + Le Pen ^1686 + Bayrou ^684 + Laguiller ^572 + Chevènement ^533 + Mamère ^525 + Besancenot ^425 + Saint-Josse ^423 + Madelin ^391 + Robert Hue ^337 + Mégret > Le Pen ^234 + Taubira ^232 + Lepage ^188 + Boutin ^119 + Gluckstein ^47 + '); + + self::assertSame( + [ + 1 => 'Chirac', + 2 => 'Le Pen', + 3 => 'Jospin', + 4 => 'Bayrou', + 5 => 'Laguiller', + 6 => 'Chevènement', + 7 => 'Mamère', + 8 => 'Besancenot', + 9 => 'Saint-Josse', + 10 => 'Madelin', + 11 => 'Robert Hue', + 12 => 'Mégret', + 13 => 'Taubira', + 14 => 'Lepage', + 15 => 'Boutin', + 16 => 'Gluckstein', + ], + $this->election->getResult('Fptp')->getResultAsArray(true) + ); + + self::assertEquals( + [1 => [ + 'Chirac' => 1988, + 'Le Pen' => 1686, + 'Jospin' => 1618, + 'Bayrou' => 684, + 'Laguiller' => 572, + 'Chevènement' => 533, + 'Mamère' => 525, + 'Besancenot' => 425, + 'Saint-Josse' => 423, + 'Madelin' => 391, + 'Robert Hue' => 337, + 'Mégret' => 234, + 'Taubira' => 232, + 'Lepage' => 188, + 'Boutin' => 119, + 'Gluckstein' => 47, + ]], + $this->election->getResult('Fptp')->getStats() + ); + } + + public function testResult_1(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D * 42 + B>C>D>A * 26 + C>D>B>A * 15 + D>C>B>A * 17 + '); + + self::assertSame( + [ + 1 => 'A', + 2 => 'B', + 3 => 'D', + 4 => 'C', ], + $this->election->getResult('Fptp')->getResultAsArray(true) + ); + + self::assertEquals( + [1 => [ + 'A' => 42, + 'B' => 26, + 'D' => 17, + 'C' => 15, + ]], + $this->election->getResult('Fptp')->getStats() + ); + } + + public function testResult_2(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + A>B>C>D ^ 42 + B>C>D>A * 26 + C>D>B>A ^ 15 + D>C>B>A * 17 + D>B=C=A ^ 25 + '); + + self::assertSame( + [ + 1 => ['A', 'D'], + 2 => 'B', + 3 => 'C', ], + $this->election->getResult('Fptp')->getResultAsArray(true) + ); + + self::assertSame( + [1 => [ + 'A' => (float) 42, + 'D' => (float) 42, + 'B' => (float) 26, + 'C' => (float) 15, + ]], + $this->election->getResult('Fptp')->getStats() + ); + } + + public function testResult_3(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A>B>C + A=C>B + '); + + self::assertSame( + [ + 1 => 'A', + 2 => 'C', + 3 => 'B', ], + $this->election->getResult('Fptp')->getResultAsArray(true) + ); + + self::assertEquals( + [1 => [ + 'A' => 1 + 1/2, + 'C' => 1/2, + 'B' => 0, + ]], + $this->election->getResult('Fptp')->getStats() + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Majority/MultipleRoundsSystemTest.php b/include/Condorcet/Tests/src/Algo/Methods/Majority/MultipleRoundsSystemTest.php new file mode 100644 index 0000000..1069fb4 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Majority/MultipleRoundsSystemTest.php @@ -0,0 +1,198 @@ +election = new Election; + } + + protected function tearDown(): void + { + $this->resetOptions(); + } + + protected function resetOptions(): void + { + $methodClass = Condorcet::getMethodClass('runoff voting'); + + $this->election->setMethodOption($methodClass, 'MAX_ROUND', 2); + $this->election->setMethodOption($methodClass, 'TARGET_NUMBER_OF_CANDIDATES_FOR_THE_NEXT_ROUND', 2); + $this->election->setMethodOption($methodClass, 'NUMBER_OF_TARGETED_CANDIDATES_AFTER_EACH_ROUND', 0); + } + + public function testResult_MajorityTest_systematic_triangular(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D * 42 + B>C>D>A * 26 + C>D>B>A * 15 + D>C>B>A * 17 + '); + + $methodClass = Condorcet::getMethodClass('Multiple Rounds System'); + + $this->election->setMethodOption($methodClass, 'MAX_ROUND', 2); + $this->election->setMethodOption($methodClass, 'TARGET_NUMBER_OF_CANDIDATES_FOR_THE_NEXT_ROUND', 3); + $this->election->setMethodOption($methodClass, 'NUMBER_OF_TARGETED_CANDIDATES_AFTER_EACH_ROUND', 0); + + self::assertSame( + [ + 1 => 'A', + 2 => 'D', + 3 => 'B', + 4 => 'C', ], + $this->election->getResult('Multiple Rounds System')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 42, + 'B' => 26, + 'D' => 17, + 'C' => 15, + ], + 2=> [ + 'A' => 42, + 'D' => 32, + 'B' => 26, + ], + ], + $this->election->getResult('Multiple Rounds System')->getStats() + ); + } + + + public function testResult_MajorityTest_three_round(): void + { + $this->election->allowsVoteWeight(true); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A>B ^10 + B ^12 + C>B ^10 + D>E>A>B ^9 + E>B ^5 + '); + + $methodClass = Condorcet::getMethodClass('runoff voting'); + + $this->election->setMethodOption($methodClass, 'MAX_ROUND', 3); + $this->election->setMethodOption($methodClass, 'TARGET_NUMBER_OF_CANDIDATES_FOR_THE_NEXT_ROUND', 2); + $this->election->setMethodOption($methodClass, 'NUMBER_OF_TARGETED_CANDIDATES_AFTER_EACH_ROUND', 0); + + + self::assertSame( + [1 => 'B', 2 => 'A', 3 => 'C', 4=> 'D', 5=> 'E'], + $this->election->getResult('Multiple Rounds System')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'B' => 12, + 'A' => 10, + 'C' => 10, + 'D' => 9, + 'E' => 5, + ], + 2=> [ + 'A' => 19, + 'B' => 17, + 'C' => 10, + ], + 3=> [ + 'B' => 27, + 'A' => 19, + ], + ], + $this->election->getResult('runoff voting')->getStats() + ); + } + + public function testResult_MajorityTest_Many_Round(): void + { + $this->election->allowsVoteWeight(true); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + $this->election->addCandidate('F'); + + $this->election->parseVotes(' + A>B>C>D>E>F ^100 + B>A>C>D>E>F ^99 + C>A>B>D>E>F ^98 + D>A=B>C>E>F ^97 + E>B>A>C>D>F ^96 + F>A>B>C>D>E ^95 + '); + + $methodClass = Condorcet::getMethodClass('Multiple Rounds System'); + + $this->election->setMethodOption($methodClass, 'MAX_ROUND', 100); + $this->election->setMethodOption($methodClass, 'TARGET_NUMBER_OF_CANDIDATES_FOR_THE_NEXT_ROUND', 5); + $this->election->setMethodOption($methodClass, 'NUMBER_OF_TARGETED_CANDIDATES_AFTER_EACH_ROUND', -1); + + self::assertSame( + [1 => 'A', 2 => 'B', 3 => 'C', 4=> 'D', 5=> 'E', 6 => 'F'], + $this->election->getResult('Multiple Rounds System')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 100, + 'B' => 99, + 'C' => 98, + 'D' => 97, + 'E' => 96, + 'F' => 95, + ], + 2=> [ + 'A' => 100 + 95, + 'B' => 99, + 'C' => 98, + 'D' => 97, + 'E' => 96, + ], + 3=> [ + 'A' => 100 + 95, + 'B' => 99 + 96, + 'C' => 98, + 'D' => 97, + ], + 4=> [ + 'A' => 100 + 95 + (97/2), + 'B' => 99 + 96 + (97/2), + 'C' => 98, + ], + 5=> [ + 'A' => 100 + 95 + (97/2) + 98, + 'B' => 99 + 96 + (97/2), + ], + ], + $this->election->getResult('runoff voting')->getStats() + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Majority/TwoRoundSystemTest.php b/include/Condorcet/Tests/src/Algo/Methods/Majority/TwoRoundSystemTest.php new file mode 100644 index 0000000..e609327 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Majority/TwoRoundSystemTest.php @@ -0,0 +1,544 @@ +election = new Election; + } + + public function testResult_French2002(): void + { + $this->election->allowsVoteWeight(true); + $this->election->setImplicitRanking(false); + + $this->election->addCandidate('Chirac'); + $this->election->addCandidate('Jospin'); + $this->election->addCandidate('Le Pen'); + $this->election->addCandidate('Bayrou'); + $this->election->addCandidate('Laguiller'); + $this->election->addCandidate('Chevènement'); + $this->election->addCandidate('Mamère'); + $this->election->addCandidate('Besancenot'); + $this->election->addCandidate('Saint-Josse'); + $this->election->addCandidate('Madelin'); + $this->election->addCandidate('Robert Hue'); + $this->election->addCandidate('Mégret'); + $this->election->addCandidate('Taubira'); + $this->election->addCandidate('Lepage'); + $this->election->addCandidate('Boutin'); + $this->election->addCandidate('Gluckstein'); + + $this->election->parseVotes(' + Chirac > Bayrou = Jospin = Madelin = Chevénement = Mamère = Robert Hue = Taubira = Lepage = Boutin > Saint-Josse ^1988 + Jospin > Chevénement = Taubira = Mamère > Bayrou > Robert Hue > Chirac = Lepage = Boutin > Madelin > Saint-Josse ^ 1618 + Le Pen > Mégret ^1686 + Bayrou > Chirac ^684 + Laguiller > Besancenot = Gluckstein > ^572 + Chevènement > Chirac ^533 + Mamère > Jospin > Chirac ^525 + Besancenot > Gluckstein = Laguillier ^425 + Saint-Josse > Chirac > Jospin ^423 + Madelin > Chirac ^391 + Robert Hue > Jospin > Chirac ^337 + Mégret > Le Pen ^234 + Taubira > Jospin > Chirac ^232 + Lepage > Chirac ^188 + Boutin > Chirac ^119 + Gluckstein > Besancenot = Laguillier ^47 + '); + + self::assertSame( + [ + 1 => 'Chirac', + 2 => 'Le Pen', + 3 => 'Jospin', + 4 => 'Bayrou', + 5 => 'Laguiller', + 6 => 'Chevènement', + 7 => 'Mamère', + 8 => 'Besancenot', + 9 => 'Saint-Josse', + 10 => 'Madelin', + 11 => 'Robert Hue', + 12 => 'Mégret', + 13 => 'Taubira', + 14 => 'Lepage', + 15 => 'Boutin', + 16 => 'Gluckstein', + ], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'Chirac' => 1988, + 'Le Pen' => 1686, + 'Jospin' => 1618, + 'Bayrou' => 684, + 'Laguiller' => 572, + 'Chevènement' => 533, + 'Mamère' => 525, + 'Besancenot' => 425, + 'Saint-Josse' => 423, + 'Madelin' => 391, + 'Robert Hue' => 337, + 'Mégret' => 234, + 'Taubira' => 232, + 'Lepage' => 188, + 'Boutin' => 119, + 'Gluckstein' => 47, + ], + 2=> [ + 'Chirac' => 7038, + 'Le Pen' => 1920, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_1(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D * 42 + B>C>D>A * 26 + C>D>B>A * 15 + D>C>B>A * 17 + '); + + self::assertSame( + [ + 1 => 'B', + 2 => 'A', + 3 => 'D', + 4 => 'C', ], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 42, + 'B' => 26, + 'D' => 17, + 'C' => 15, + ], + 2=> [ + 'B' => 58, + 'A' => 42, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_2(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + A>B>C>D ^ 42 + B>C>D>A * 26 + C>D>B>A ^ 15 + D>C>B>A * 17 + D>B=C=A ^ 25 + '); + + self::assertSame( + [ + 1 => 'D', + 2 => 'A', + 3 => 'B', + 4 => 'C', ], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 42, + 'D' => 42, + 'B' => 26, + 'C' => 15, + ], + 2=> [ + 'D' => 83, + 'A' => 42, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_3(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A>B>C + A=C>B + '); + + self::assertSame( + [ + 1 => 'A', + 2 => 'C', + 3 => 'B', ], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 1.5, + 'C' => 0.5, + 'B' => 0, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_5(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>B>C>D * 51 + B>C>D>A * 24 + C>D>B>A * 25 + '); + + self::assertSame( + [ + 1 => 'A', + 2 => 'C', + 3 => 'B', + 4 => 'D', ], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 51, + 'C' => 25, + 'B' => 24, + 'D' => 0, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_6(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A>B>C>D * 50 + B>C>D>A * 50 + '); + + self::assertSame( + [1 => ['A', 'B'], 2 => 'C'], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 50, + 'B' => 50, + 'C' => 0, + ], + 2=> [ + 'A' => 50, + 'B' => 50, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_7(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + + $this->election->parseVotes(' + A>B>C>D * 50 + B>C>D>A * 50 + '); + + self::assertSame( + [1 => ['A', 'B']], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 50, + 'B' => 50, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_8(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + D>E * 50 + E>D * 50 + '); + + self::assertSame( + [1 => ['A', 'B', 'C']], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + $stats = $this->election->getResult('Two Rounds')->getStats(); + // \array_walk_recursive($stats, function (float &$value): float { + // return $value = round($value, 10); + // }); + + self::assertSame( + [1=> [ + 'A' => round(100/3, MultipleRoundsSystem::DECIMAL_PRECISION), + 'B' => round(100/3, MultipleRoundsSystem::DECIMAL_PRECISION), + 'C' => round(100/3, MultipleRoundsSystem::DECIMAL_PRECISION), + ], + ], + $stats + ); + + $this->election->setImplicitRanking(false); + + self::assertSame( + [1 => ['A', 'B', 'C']], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertSame( + [1=> [ + 'A' => 0.0, + 'B' => 0.0, + 'C' => 0.0, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_9(): void + { + $this->election->allowsVoteWeight(true); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A>B ^10 + B ^12 + C ^10 + D>E>A>B ^9 + E>B ^5 + '); + + self::assertSame( + [1 => 'A', 2 => 'B', 3 => 'C', 4=> 'D', 5=> 'E'], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'B' => 12, + 'A' => 10, + 'C' => 10, + 'D' => 9, + 'E' => 5, + ], + 2=> [ + 'A' => 19, + 'B' => 17, + 'C' => 10, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + + $this->election->addVote('E>B ^2'); + + self::assertSame( + [1 => ['A', 'B'], 2 => 'C', 3=> 'D', 4 => 'E'], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'B' => 12, + 'A' => 10, + 'C' => 10, + 'D' => 9, + 'E' => 7, + ], + 2=> [ + 'A' => 19, + 'B' => 19, + 'C' => 10, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + + $this->election->addVote('C'); + + self::assertSame( + [1 => 'B', 2 => 'C', 3=> 'A', 4 => 'D', 5 => 'E'], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'B' => 12, + 'C' => 11, + 'A' => 10, + 'D' => 9, + 'E' => 7, + ], + 2=> [ + 'B' => 38, + 'C' => 11, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_10(): void + { + $this->election->allowsVoteWeight(true); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A ^10 + B ^10 + C ^10 + D>E ^9 + '); + + self::assertSame( + [1 => ['A', 'B', 'C'], 2 => 'D', 3 => 'E'], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertSame( + [1=> [ + 'A' => (float) 10, + 'B' => (float) 10, + 'C' => (float) 10, + 'D' => (float) 9, + 'E' => (float) 0, + ], + 2=> [ + 'A' => (float) 13, + 'B' => (float) 13, + 'C' => (float) 13, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + + $this->election->setImplicitRanking(false); + + self::assertSame( + [1 => ['A', 'B', 'C'], 2 => 'D', 3 => 'E'], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertEquals( + [1=> [ + 'A' => 10, + 'B' => 10, + 'C' => 10, + 'D' => 9, + 'E' => 0, + ], + 2=> [ + 'A' => 10, + 'B' => 10, + 'C' => 10, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } + + public function testResult_11(): void + { + $this->election->allowsVoteWeight(true); + $this->election->setImplicitRanking(false); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A ^10 + B ^10 + C ^10 + D>E ^9 + '); + + self::assertSame( + [1 => ['A', 'B', 'C']], + $this->election->getResult('Two Rounds')->getResultAsArray(true) + ); + + self::assertSame( + [1=> [ + 'A' => (float) 10, + 'B' => (float) 10, + 'C' => (float) 10, + ], + ], + $this->election->getResult('Two Rounds')->getStats() + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Minimax/MinimaxTest.php b/include/Condorcet/Tests/src/Algo/Methods/Minimax/MinimaxTest.php new file mode 100644 index 0000000..69f3244 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Minimax/MinimaxTest.php @@ -0,0 +1,273 @@ +election = new Election; + } + + public function testResult_1(): void + { + # From https://en.wikipedia.org/wiki/Minimax_Condorcet + + $this->election->addCandidate('Memphis'); + $this->election->addCandidate('Nashville'); + $this->election->addCandidate('Chattanooga'); + $this->election->addCandidate('Knoxville'); + + $this->election->parseVotes(' + Memphis > Nashville > Chattanooga * 42 + Nashville > Chattanooga > Knoxville * 26 + Chattanooga > Knoxville > Nashville * 15 + Knoxville > Chattanooga > Nashville * 17 + '); + + self::assertSame($this->election->getWinner(), $this->election->getWinner('Minimax Winning')); + + self::assertSame($this->election->getWinner(), $this->election->getWinner('Minimax Margin')); + + self::assertSame($this->election->getWinner(), $this->election->getWinner('Minimax Opposition')); + + $expectedRanking = [ + 1 => 'Nashville', + 2 => 'Memphis', + 3 => 'Chattanooga', + 4 => 'Knoxville', + ]; + + self::assertSame( + $expectedRanking, + $this->election->getResult('Minimax Winning')->getResultAsArray(true) + ); + + self::assertSame( + $expectedRanking, + $this->election->getResult('Minimax Margin')->getResultAsArray(true) + ); + + self::assertSame( + $expectedRanking, + $this->election->getResult('Minimax Opposition')->getResultAsArray(true) + ); + + self::assertSame( + ['Memphis' => ['worst_pairwise_defeat_winning' => 58], + 'Nashville' => ['worst_pairwise_defeat_winning' => 0], + 'Chattanooga' => ['worst_pairwise_defeat_winning' => 68], + 'Knoxville' => ['worst_pairwise_defeat_winning' => 83], ], + $this->election->getResult('Minimax Winning')->getStats() + ); + + self::assertSame( + ['Memphis' => ['worst_pairwise_defeat_margin' => 16], + 'Nashville' => ['worst_pairwise_defeat_margin' => -16], + 'Chattanooga' => ['worst_pairwise_defeat_margin' => 36], + 'Knoxville' => ['worst_pairwise_defeat_margin' => 66], ], + $this->election->getResult('Minimax Margin')->getStats() + ); + + self::assertSame( + ['Memphis' => ['worst_pairwise_opposition' => 58], + 'Nashville' => ['worst_pairwise_opposition' => 42], + 'Chattanooga' => ['worst_pairwise_opposition' => 68], + 'Knoxville' => ['worst_pairwise_opposition' => 83], ], + $this->election->getResult('Minimax Opposition')->getStats() + ); + } + + public function testResult_2(): void + { + # From https://en.wikipedia.org/wiki/Minimax_Condorcet + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A = C > B * 4 + A > C > B * 47 + C > B > A * 43 + B > A = C * 6 + '); + + self::assertSame($this->election->getWinner(), $this->election->getWinner('Minimax Winning')); + + self::assertSame($this->election->getWinner(), $this->election->getWinner('Minimax Margin')); + + self::assertEquals('C', $this->election->getWinner('Minimax Opposition')); + + $expectedRanking1 = [ + 1 => 'A', + 2 => 'C', + 3 => 'B', + ]; + + self::assertSame( + $expectedRanking1, + $this->election->getResult('Minimax Winning')->getResultAsArray(true) + ); + + self::assertSame( + $expectedRanking1, + $this->election->getResult('Minimax Margin')->getResultAsArray(true) + ); + + self::assertSame( + [1 => 'C', + 2 => 'A', + 3 => 'B', ], + $this->election->getResult('Minimax Opposition')->getResultAsArray(true) + ); + + self::assertSame( + ['A' => ['worst_pairwise_defeat_winning' => 0], + 'B' => ['worst_pairwise_defeat_winning' => 94], + 'C' => ['worst_pairwise_defeat_winning' => 47], ], + $this->election->getResult('Minimax Winning')->getStats() + ); + + self::assertSame( + ['A' => ['worst_pairwise_defeat_margin' => -2], + 'B' => ['worst_pairwise_defeat_margin' => 88], + 'C' => ['worst_pairwise_defeat_margin' => 4], ], + $this->election->getResult('Minimax Margin')->getStats() + ); + + self::assertSame( + ['A' => ['worst_pairwise_opposition' => 49], + 'B' => ['worst_pairwise_opposition' => 94], + 'C' => ['worst_pairwise_opposition' => 47], ], + $this->election->getResult('Minimax Opposition')->getStats() + ); + } + + public function testResult_3(): void + { + # From http://www.cs.wustl.edu/~legrand/rbvote/desc.html + + $this->election->addCandidate('Abby'); + $this->election->addCandidate('Brad'); + $this->election->addCandidate('Cora'); + $this->election->addCandidate('Dave'); + $this->election->addCandidate('Erin'); + + $this->election->parseVotes(' + Abby>Cora>Erin>Dave>Brad * 98 + Brad>Abby>Erin>Cora>Dave * 64 + Brad>Abby>Erin>Dave>Cora * 12 + Brad>Erin>Abby>Cora>Dave * 98 + Brad>Erin>Abby>Dave>Cora * 13 + Brad>Erin>Dave>Abby>Cora * 125 + Cora>Abby>Erin>Dave>Brad * 124 + Cora>Erin>Abby>Dave>Brad * 76 + Dave>Abby>Brad>Erin>Cora * 21 + Dave>Brad>Abby>Erin>Cora * 30 + Dave>Brad>Erin>Cora>Abby * 98 + Dave>Cora>Abby>Brad>Erin * 139 + Dave>Cora>Brad>Abby>Erin * 23 + '); + + self::assertEquals('Cora', $this->election->getWinner('Minimax Winning')); + } + + public function testResult_4(): void + { + # From https://en.wikipedia.org/wiki/Condorcet_loser_criterion + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('L'); + + $this->election->parseVotes(' + A > B > C * 1 + A > B > L * 1 + B > C > A * 3 + C > L > A * 1 + L > A > B * 1 + L > C > A * 2 + '); + + self::assertEquals('L', $this->election->getWinner('Minimax Winning')); + } + + public function testResult_5(): void + { + # From https://en.wikipedia.org/wiki/Condorcet_loser_criterion + + $this->election->setImplicitRanking(false); + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A > C > B > D * 30 + D > B > A > C * 15 + D > B > C > A * 14 + B > C > A > D * 6 + D > C > A = B * 4 + C > A = B * 16 + B > C * 14 + C > A * 3 + '); + + self::assertEquals('A', $this->election->getWinner('Minimax Winning')); + self::assertEquals('B', $this->election->getWinner('Minimax Margin')); + self::assertEquals('D', $this->election->getWinner('Minimax Opposition')); + + self::assertSame( + ['A' => ['worst_pairwise_defeat_winning' => 35], + 'B' => ['worst_pairwise_defeat_winning' => 50], + 'C' => ['worst_pairwise_defeat_winning' => 45], + 'D' => ['worst_pairwise_defeat_winning' => 36], ], + $this->election->getResult('Minimax Winning')->getStats() + ); + self::assertSame( + ['A' => ['worst_pairwise_defeat_margin' => 5], + 'B' => ['worst_pairwise_defeat_margin' => 1], + 'C' => ['worst_pairwise_defeat_margin' => 2], + 'D' => ['worst_pairwise_defeat_margin' => 3], ], + $this->election->getResult('Minimax Margin')->getStats() + ); + self::assertSame( + ['A' => ['worst_pairwise_opposition' => 43], + 'B' => ['worst_pairwise_opposition' => 50], + 'C' => ['worst_pairwise_opposition' => 49], + 'D' => ['worst_pairwise_opposition' => 36], ], + $this->election->getResult('Minimax Opposition')->getStats() + ); + + // Implicit Ranking + $this->election->setImplicitRanking(true); + + self::assertNotEquals('A', $this->election->getWinner('Minimax Winning')); + } + + public function testResult_6(): void + { + # From https://en.wikipedia.org/wiki/Minimax_Condorcet + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A > B > C + C > B > A + '); + + self::assertNotNull($this->election->getWinner('Minimax Margin')); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/PairwiseTest.php b/include/Condorcet/Tests/src/Algo/Methods/PairwiseTest.php new file mode 100644 index 0000000..15c1e8c --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/PairwiseTest.php @@ -0,0 +1,176 @@ +election1 = new Election; + + $this->election1->addCandidate('A'); + $this->election1->addCandidate('B'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('A>B>C'); + } + + public function testPairwiseOffsetGet(): void + { + $pairwise = $this->election1->getPairwise(); + + self::assertIsArray($pairwise[1]); + + self::assertNull($pairwise[42]); + } + + public function testExplicitPairwise(): void + { + self::assertSame( + [ + 'A' => [ + 'win' => [ + 'B' => 1, + 'C' => 1, + ], + 'null' => [ + 'B' => 0, + 'C' => 0, + ], + 'lose' => [ + 'B' => 0, + 'C' => 0, + ], + ], + 'B' => [ + 'win' => [ + 'A' => 0, + 'C' => 1, + ], + 'null' => [ + 'A' => 0, + 'C' => 0, + ], + 'lose' => [ + 'A' => 1, + 'C' => 0, + ], + ], + 'C' => [ + 'win' => [ + 'A' => 0, + 'B' => 0, + ], + 'null' => [ + 'A' => 0, + 'B' => 0, + ], + 'lose' => [ + 'A' => 1, + 'B' => 1, + ], + ], + ], + $this->election1->getPairwise()->getExplicitPairwise() + ); + } + + public function testVotesWeight(): void + { + $electionOff = new Election; + + $electionOff->addCandidate('A'); + $electionOff->addCandidate('B'); + $electionOff->addCandidate('C'); + $electionOff->addCandidate('D'); + + $electionOff->addVote('A>B>C=D ^3'); + $electionOff->addVote('A>B>C=D ^4'); + + $electionOn = clone $electionOff; + $electionOn->allowsVoteWeight(true); + + self::assertNotSame($electionOff->getExplicitPairwise(), $electionOn->getExplicitPairwise()); + + self::assertSame( + [ + 'A' => [ + 'win' => [ + 'B' => 7, + 'C' => 7, + 'D' => 7, + ], + 'null' => [ + 'B' => 0, + 'C' => 0, + 'D' => 0, + ], + 'lose' => [ + 'B' => 0, + 'C' => 0, + 'D' => 0, + ], + ], + 'B' => [ + 'win' => [ + 'A' => 0, + 'C' => 7, + 'D' => 7, + ], + 'null' => [ + 'A' => 0, + 'C' => 0, + 'D' => 0, + ], + 'lose' => [ + 'A' => 7, + 'C' => 0, + 'D' => 0, + ], + ], + 'C' => [ + 'win' => [ + 'A' => 0, + 'B' => 0, + 'D' => 0, + ], + 'null' => [ + 'A' => 0, + 'B' => 0, + 'D' => 7, + ], + 'lose' => [ + 'A' => 7, + 'B' => 7, + 'D' => 0, + ], + ], + 'D' => [ + 'win' => [ + 'A' => 0, + 'B' => 0, + 'C' => 0, + ], + 'null' => [ + 'A' => 0, + 'B' => 0, + 'C' => 7, + ], + 'lose' => [ + 'A' => 7, + 'B' => 7, + 'C' => 0, + ], + ], + ], + $electionOn->getPairwise()->getExplicitPairwise() + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/RankedPairs/RankedPairsTest.php b/include/Condorcet/Tests/src/Algo/Methods/RankedPairs/RankedPairsTest.php new file mode 100644 index 0000000..7bc7763 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/RankedPairs/RankedPairsTest.php @@ -0,0 +1,464 @@ +election = new Election; + } + + public function testResult_1(): void + { + # From https://fr.wikipedia.org/wiki/M%C3%A9thode_Condorcet_avec_rangement_des_paires_par_ordre_d%C3%A9croissant + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A > C > B > E * 5 + A > D > E > C * 5 + B > E > D > A * 8 + C > A > B > E * 3 + C > A > E > B * 7 + C > B > A > D * 2 + D > C > E > B * 7 + E > B > A > D * 8 + '); + + self::assertEquals('A', $this->election->getWinner('Ranked Pairs Winning')); + + $expected = [1 => 'A', + 2 => 'C', + 3 => 'E', + 4 => 'B', + 5 => 'D', ]; + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true) + ); + + self::assertSame( + unserialize('a:2:{s:5:"tally";a:10:{i:0;a:1:{i:0;a:5:{s:4:"from";s:1:"B";s:2:"to";s:1:"D";s:3:"win";i:33;s:8:"minority";i:12;s:6:"margin";i:21;}}i:1;a:1:{i:0;a:5:{s:4:"from";s:1:"E";s:2:"to";s:1:"D";s:3:"win";i:31;s:8:"minority";i:14;s:6:"margin";i:17;}}i:2;a:1:{i:0;a:5:{s:4:"from";s:1:"A";s:2:"to";s:1:"D";s:3:"win";i:30;s:8:"minority";i:15;s:6:"margin";i:15;}}i:3;a:1:{i:0;a:5:{s:4:"from";s:1:"C";s:2:"to";s:1:"B";s:3:"win";i:29;s:8:"minority";i:16;s:6:"margin";i:13;}}i:4;a:1:{i:0;a:5:{s:4:"from";s:1:"D";s:2:"to";s:1:"C";s:3:"win";i:28;s:8:"minority";i:17;s:6:"margin";i:11;}}i:5;a:1:{i:0;a:5:{s:4:"from";s:1:"E";s:2:"to";s:1:"B";s:3:"win";i:27;s:8:"minority";i:18;s:6:"margin";i:9;}}i:6;a:1:{i:0;a:5:{s:4:"from";s:1:"A";s:2:"to";s:1:"C";s:3:"win";i:26;s:8:"minority";i:19;s:6:"margin";i:7;}}i:7;a:1:{i:0;a:5:{s:4:"from";s:1:"B";s:2:"to";s:1:"A";s:3:"win";i:25;s:8:"minority";i:20;s:6:"margin";i:5;}}i:8;a:1:{i:0;a:5:{s:4:"from";s:1:"C";s:2:"to";s:1:"E";s:3:"win";i:24;s:8:"minority";i:21;s:6:"margin";i:3;}}i:9;a:1:{i:0;a:5:{s:4:"from";s:1:"E";s:2:"to";s:1:"A";s:3:"win";i:23;s:8:"minority";i:22;s:6:"margin";i:1;}}}s:4:"arcs";a:7:{i:0;a:2:{s:4:"from";s:1:"B";s:2:"to";s:1:"D";}i:1;a:2:{s:4:"from";s:1:"E";s:2:"to";s:1:"D";}i:2;a:2:{s:4:"from";s:1:"A";s:2:"to";s:1:"D";}i:3;a:2:{s:4:"from";s:1:"C";s:2:"to";s:1:"B";}i:4;a:2:{s:4:"from";s:1:"E";s:2:"to";s:1:"B";}i:5;a:2:{s:4:"from";s:1:"A";s:2:"to";s:1:"C";}i:6;a:2:{s:4:"from";s:1:"C";s:2:"to";s:1:"E";}}}'), + $this->election->getResult('Ranked Pairs Winning')->getStats() + ); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + + self::assertSame( + unserialize('a:2:{s:5:"tally";a:10:{i:0;a:1:{i:0;a:5:{s:4:"from";s:1:"B";s:2:"to";s:1:"D";s:3:"win";i:33;s:8:"minority";i:12;s:6:"margin";i:21;}}i:1;a:1:{i:0;a:5:{s:4:"from";s:1:"E";s:2:"to";s:1:"D";s:3:"win";i:31;s:8:"minority";i:14;s:6:"margin";i:17;}}i:2;a:1:{i:0;a:5:{s:4:"from";s:1:"A";s:2:"to";s:1:"D";s:3:"win";i:30;s:8:"minority";i:15;s:6:"margin";i:15;}}i:3;a:1:{i:0;a:5:{s:4:"from";s:1:"C";s:2:"to";s:1:"B";s:3:"win";i:29;s:8:"minority";i:16;s:6:"margin";i:13;}}i:4;a:1:{i:0;a:5:{s:4:"from";s:1:"D";s:2:"to";s:1:"C";s:3:"win";i:28;s:8:"minority";i:17;s:6:"margin";i:11;}}i:5;a:1:{i:0;a:5:{s:4:"from";s:1:"E";s:2:"to";s:1:"B";s:3:"win";i:27;s:8:"minority";i:18;s:6:"margin";i:9;}}i:6;a:1:{i:0;a:5:{s:4:"from";s:1:"A";s:2:"to";s:1:"C";s:3:"win";i:26;s:8:"minority";i:19;s:6:"margin";i:7;}}i:7;a:1:{i:0;a:5:{s:4:"from";s:1:"B";s:2:"to";s:1:"A";s:3:"win";i:25;s:8:"minority";i:20;s:6:"margin";i:5;}}i:8;a:1:{i:0;a:5:{s:4:"from";s:1:"C";s:2:"to";s:1:"E";s:3:"win";i:24;s:8:"minority";i:21;s:6:"margin";i:3;}}i:9;a:1:{i:0;a:5:{s:4:"from";s:1:"E";s:2:"to";s:1:"A";s:3:"win";i:23;s:8:"minority";i:22;s:6:"margin";i:1;}}}s:4:"arcs";a:7:{i:0;a:2:{s:4:"from";s:1:"B";s:2:"to";s:1:"D";}i:1;a:2:{s:4:"from";s:1:"E";s:2:"to";s:1:"D";}i:2;a:2:{s:4:"from";s:1:"A";s:2:"to";s:1:"D";}i:3;a:2:{s:4:"from";s:1:"C";s:2:"to";s:1:"B";}i:4;a:2:{s:4:"from";s:1:"E";s:2:"to";s:1:"B";}i:5;a:2:{s:4:"from";s:1:"A";s:2:"to";s:1:"C";}i:6;a:2:{s:4:"from";s:1:"C";s:2:"to";s:1:"E";}}}'), + $this->election->getResult('Ranked Pairs Margin')->getStats() + ); + } + + public function testResult_2(): void + { + # From https://en.wikipedia.org/wiki/Ranked_pairs + + $this->election->addCandidate('Memphis'); + $this->election->addCandidate('Nashville'); + $this->election->addCandidate('Knoxville'); + $this->election->addCandidate('Chattanooga'); + + $this->election->parseVotes(' + Memphis > Nashville > Chattanooga * 42 + Nashville > Chattanooga > Knoxville * 26 + Chattanooga > Knoxville > Nashville * 15 + Knoxville > Chattanooga > Nashville * 17 + '); + + $expected = [1 => 'Nashville', + 2 => 'Chattanooga', + 3 => 'Knoxville', + 4 => 'Memphis', ]; + + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true) + ); + + self::assertSame( + unserialize('a:2:{s:5:"tally";a:3:{i:0;a:1:{i:0;a:5:{s:4:"from";s:11:"Chattanooga";s:2:"to";s:9:"Knoxville";s:3:"win";i:83;s:8:"minority";i:17;s:6:"margin";i:66;}}i:1;a:2:{i:0;a:5:{s:4:"from";s:9:"Nashville";s:2:"to";s:9:"Knoxville";s:3:"win";i:68;s:8:"minority";i:32;s:6:"margin";i:36;}i:1;a:5:{s:4:"from";s:9:"Nashville";s:2:"to";s:11:"Chattanooga";s:3:"win";i:68;s:8:"minority";i:32;s:6:"margin";i:36;}}i:2;a:3:{i:0;a:5:{s:4:"from";s:9:"Nashville";s:2:"to";s:7:"Memphis";s:3:"win";i:58;s:8:"minority";i:42;s:6:"margin";i:16;}i:1;a:5:{s:4:"from";s:9:"Knoxville";s:2:"to";s:7:"Memphis";s:3:"win";i:58;s:8:"minority";i:42;s:6:"margin";i:16;}i:2;a:5:{s:4:"from";s:11:"Chattanooga";s:2:"to";s:7:"Memphis";s:3:"win";i:58;s:8:"minority";i:42;s:6:"margin";i:16;}}}s:4:"arcs";a:6:{i:0;a:2:{s:4:"from";s:11:"Chattanooga";s:2:"to";s:9:"Knoxville";}i:1;a:2:{s:4:"from";s:9:"Nashville";s:2:"to";s:9:"Knoxville";}i:2;a:2:{s:4:"from";s:9:"Nashville";s:2:"to";s:11:"Chattanooga";}i:3;a:2:{s:4:"from";s:9:"Nashville";s:2:"to";s:7:"Memphis";}i:4;a:2:{s:4:"from";s:9:"Knoxville";s:2:"to";s:7:"Memphis";}i:5;a:2:{s:4:"from";s:11:"Chattanooga";s:2:"to";s:7:"Memphis";}}}'), + $this->election->getResult('Ranked Pairs Winning')->getStats() + ); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + + self::assertSame( + unserialize('a:2:{s:5:"tally";a:3:{i:0;a:1:{i:0;a:5:{s:4:"from";s:11:"Chattanooga";s:2:"to";s:9:"Knoxville";s:3:"win";i:83;s:8:"minority";i:17;s:6:"margin";i:66;}}i:1;a:2:{i:0;a:5:{s:4:"from";s:9:"Nashville";s:2:"to";s:9:"Knoxville";s:3:"win";i:68;s:8:"minority";i:32;s:6:"margin";i:36;}i:1;a:5:{s:4:"from";s:9:"Nashville";s:2:"to";s:11:"Chattanooga";s:3:"win";i:68;s:8:"minority";i:32;s:6:"margin";i:36;}}i:2;a:3:{i:0;a:5:{s:4:"from";s:9:"Nashville";s:2:"to";s:7:"Memphis";s:3:"win";i:58;s:8:"minority";i:42;s:6:"margin";i:16;}i:1;a:5:{s:4:"from";s:9:"Knoxville";s:2:"to";s:7:"Memphis";s:3:"win";i:58;s:8:"minority";i:42;s:6:"margin";i:16;}i:2;a:5:{s:4:"from";s:11:"Chattanooga";s:2:"to";s:7:"Memphis";s:3:"win";i:58;s:8:"minority";i:42;s:6:"margin";i:16;}}}s:4:"arcs";a:6:{i:0;a:2:{s:4:"from";s:11:"Chattanooga";s:2:"to";s:9:"Knoxville";}i:1;a:2:{s:4:"from";s:9:"Nashville";s:2:"to";s:9:"Knoxville";}i:2;a:2:{s:4:"from";s:9:"Nashville";s:2:"to";s:11:"Chattanooga";}i:3;a:2:{s:4:"from";s:9:"Nashville";s:2:"to";s:7:"Memphis";}i:4;a:2:{s:4:"from";s:9:"Knoxville";s:2:"to";s:7:"Memphis";}i:5;a:2:{s:4:"from";s:11:"Chattanooga";s:2:"to";s:7:"Memphis";}}}'), + $this->election->getResult('Ranked Pairs Margin')->getStats() + ); + } + + public function testResult_3(): void + { + # from http://www.cs.wustl.edu/~legrand/rbvote/desc.html + + $this->election->addCandidate('Abby'); + $this->election->addCandidate('Brad'); + $this->election->addCandidate('Cora'); + $this->election->addCandidate('Dave'); + $this->election->addCandidate('Erin'); + + $this->election->parseVotes(' + Abby>Cora>Erin>Dave>Brad * 98 + Brad>Abby>Erin>Cora>Dave * 64 + Brad>Abby>Erin>Dave>Cora * 12 + Brad>Erin>Abby>Cora>Dave * 98 + Brad>Erin>Abby>Dave>Cora * 13 + Brad>Erin>Dave>Abby>Cora * 125 + Cora>Abby>Erin>Dave>Brad * 124 + Cora>Erin>Abby>Dave>Brad * 76 + Dave>Abby>Brad>Erin>Cora * 21 + Dave>Brad>Abby>Erin>Cora * 30 + Dave>Brad>Erin>Cora>Abby * 98 + Dave>Cora>Abby>Brad>Erin * 139 + Dave>Cora>Brad>Abby>Erin * 23 + '); + + $expected =[1 => 'Brad', + 2 => 'Abby', + 3 => 'Erin', + 4 => 'Dave', + 5 => 'Cora', ]; + + self::assertEquals('Brad', $this->election->getWinner('Ranked Pairs Winning')); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true) + ); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + } + + public function testResult_4(): void + { + # From https://en.wikipedia.org/wiki/Ranked_pairs + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A > B * 68 + B > C * 72 + C > A * 52 + '); + + // Not supporting not ranked candidate + self::assertNotEquals('A', $this->election->getWinner('Ranked Pairs Winning')); + + // Supporting not ranked candidate + $this->election->setImplicitRanking(false); + self::assertEquals('A', $this->election->getWinner('Ranked Pairs Winning')); + } + + public function testResult_5(): void + { + # From http://ericgorr.net/condorcet/rankedpairs/example1/ + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A > B > C * 7 + B > A > C * 5 + C > A > B * 4 + B > C > A * 2 + '); + + $expected = [1 => 'A', + 2 => 'B', + 3 => 'C', ]; + + self::assertEquals('A', $this->election->getWinner('Ranked Pairs Winning')); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true) + ); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + } + + public function testResult_6(): void + { + # From http://ericgorr.net/condorcet/rankedpairs/example2/ + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A > B > C * 40 + B > C > A * 35 + C > A > B * 25 + '); + + $expected = [1 => 'A', + 2 => 'B', + 3 => 'C', ]; + + self::assertEquals('A', $this->election->getWinner('Ranked Pairs Winning')); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true) + ); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + } + + public function testResult_7(): void + { + # From http://ericgorr.net/condorcet/rankedpairs/example3/ + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + A > B > C * 7 + B > A > C * 7 + C > A > B * 2 + C > B > A * 2 + '); + + $expected = [1 => ['A', 'B'], + 2 => 'C', ]; + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true) + ); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + } + + public function testResult_8(): void + { + # From http://ericgorr.net/condorcet/rankedpairs/example4/ + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A>D>C>B*12 + B>A>C>D*3 + B>C>A>D*25 + C>B>A>D*21 + D>A>B>C*12 + D>A>C>B*21 + D>B>A>C*6 + '); + + $expected = [1 => 'B', + 2 => 'A', + 3 => 'D', + 4 => 'C', ]; + + self::assertEquals('B', $this->election->getWinner('Ranked Pairs Winning')); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true) + ); + + self::assertSame( + unserialize('a:2:{s:5:"tally";a:4:{i:0;a:1:{i:0;a:5:{s:4:"from";s:1:"A";s:2:"to";s:1:"D";s:3:"win";i:61;s:8:"minority";i:39;s:6:"margin";i:22;}}i:1;a:1:{i:0;a:5:{s:4:"from";s:1:"B";s:2:"to";s:1:"A";s:3:"win";i:55;s:8:"minority";i:45;s:6:"margin";i:10;}}i:2;a:2:{i:0;a:5:{s:4:"from";s:1:"A";s:2:"to";s:1:"C";s:3:"win";i:54;s:8:"minority";i:46;s:6:"margin";i:8;}i:1;a:5:{s:4:"from";s:1:"C";s:2:"to";s:1:"B";s:3:"win";i:54;s:8:"minority";i:46;s:6:"margin";i:8;}}i:3;a:2:{i:0;a:5:{s:4:"from";s:1:"D";s:2:"to";s:1:"B";s:3:"win";i:51;s:8:"minority";i:49;s:6:"margin";i:2;}i:1;a:5:{s:4:"from";s:1:"D";s:2:"to";s:1:"C";s:3:"win";i:51;s:8:"minority";i:49;s:6:"margin";i:2;}}}s:4:"arcs";a:3:{i:0;a:2:{s:4:"from";s:1:"A";s:2:"to";s:1:"D";}i:1;a:2:{s:4:"from";s:1:"B";s:2:"to";s:1:"A";}i:2;a:2:{s:4:"from";s:1:"D";s:2:"to";s:1:"C";}}}'), + $this->election->getResult('Ranked Pairs Winning')->getStats() + ); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + + self::assertSame( + unserialize('a:2:{s:5:"tally";a:4:{i:0;a:1:{i:0;a:5:{s:4:"from";s:1:"A";s:2:"to";s:1:"D";s:3:"win";i:61;s:8:"minority";i:39;s:6:"margin";i:22;}}i:1;a:1:{i:0;a:5:{s:4:"from";s:1:"B";s:2:"to";s:1:"A";s:3:"win";i:55;s:8:"minority";i:45;s:6:"margin";i:10;}}i:2;a:2:{i:0;a:5:{s:4:"from";s:1:"A";s:2:"to";s:1:"C";s:3:"win";i:54;s:8:"minority";i:46;s:6:"margin";i:8;}i:1;a:5:{s:4:"from";s:1:"C";s:2:"to";s:1:"B";s:3:"win";i:54;s:8:"minority";i:46;s:6:"margin";i:8;}}i:3;a:2:{i:0;a:5:{s:4:"from";s:1:"D";s:2:"to";s:1:"B";s:3:"win";i:51;s:8:"minority";i:49;s:6:"margin";i:2;}i:1;a:5:{s:4:"from";s:1:"D";s:2:"to";s:1:"C";s:3:"win";i:51;s:8:"minority";i:49;s:6:"margin";i:2;}}}s:4:"arcs";a:3:{i:0;a:2:{s:4:"from";s:1:"A";s:2:"to";s:1:"D";}i:1;a:2:{s:4:"from";s:1:"B";s:2:"to";s:1:"A";}i:2;a:2:{s:4:"from";s:1:"D";s:2:"to";s:1:"C";}}}'), + $this->election->getResult('Ranked Pairs Winning')->getStats() + ); + } + + public function testResult_9(): void + { + # Test fix for rare bug + + for ($i=0; $i < 8; $i++) { + $this->election->addCandidate(); + } + + $this->election->parseVotes(' + A > E > B > H > G > F > D > C * 1 + B > F > E > H > C > A > G > D * 1 + G > F > B > C > D > E > H > A * 1 + H > A > B > F > E > C > D > G * 1 + B > H > A > E > G > F > D > C * 1 + E > D > H > C > B > A > F > G * 1 + C > A > F > B > E > D > H > G * 1 + G > H > D > C > E > F > B > A * 1 + F > E > H > A > B > C > G > D * 1 + D > B > F > C > G > E > A > H * 1 + H > G > A > E > B > C > F > D * 1 + E > D > G > F > A > B > H > C * 1 + C > D > G > A > E > H > B > F * 1 + H > C > B > G > A > D > F > E * 1 + C > B > G > A > D > H > F > E * 1 + B > D > F > H > G > E > A > C * 1 + B > C > E > F > G > H > D > A * 1 + C > G > H > F > D > E > A > B * 1 + E > A > H > C > F > D > G > B * 1 + C > D > G > H > B > A > E > F * 1 + B > D > A > C > G > F > E > H * 1 + C > A > B > G > E > D > H > F * 1 + E > G > H > A > D > C > F > B * 1 + F > G > B > H > E > C > D > A * 1 + A > H > D > C > F > E > B > G * 1 + '); + + self::assertEquals('B', $this->election->getWinner('Ranked Pairs Winning')); + } + + public function testResult_10(): void + { + # Tideman: Independence of Clones as a Criterion for Voting Rules (1987) + # Example 5 + + $this->election->addCandidate('v'); + $this->election->addCandidate('w'); + $this->election->addCandidate('x'); + $this->election->addCandidate('y'); + $this->election->addCandidate('z'); + + $this->election->parseVotes(' + v>w>x>y>z*7 + z>y>v>w>x*3 + y>z>w>x>v*6 + w>x>v>z>y*3 + z>x>v>w>y*5 + y>x>v>w>z*3 + '); + + + self::assertEquals('v', $this->election->getWinner('Ranked Pairs Winning')); + + $expected = [1 => 'v', + 2 => 'w', + 3 => 'x', + 4 => 'y', + 5 => 'z', ]; + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true) + ); + + self::assertSame( + $expected, + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + } + + public function testResult_11(): void + { + # From http://rangevoting.org/WinningVotes.htmls + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + + $this->election->parseVotes(' + B > C > A * 9 + C = A > B * 6 + A > B > C * 5 + '); + + self::assertNotEquals( + $this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true), + $this->election->getResult('Ranked Pairs Margin')->getResultAsArray(true) + ); + } + + public function testMaxCandidates(): never + { + $this->expectException(CandidatesMaxNumberReachedException::class); + $this->expectExceptionMessage("Maximum number of candidates reached: The method 'Ranked Pairs Winning' is configured to accept only 60 candidates"); + + for ($i=0; $i < 61; $i++) { + $this->election->addCandidate(); + } + + $this->election->parseVotes('A'); + + $this->election->getWinner('Ranked Pairs Winning'); + } + + // public function testResult_stressTests (): void + // { + // $rounds = 1; + // $candidates = 332; + // $votes = 500; + + // # Test fix for rare bug + // for ($j=0; $j < $rounds; $j++) { + // $this->election = new Election; + + // for ($i=0; $i < $candidates ; $i++) { + // $this->election->addCandidate(); + // } + + + // $VoteModel = $this->election->getCandidatesList(); + // \shuffle($VoteModel); + + // for ($i = 0 ; $i < $votes ; $i++) { + // \shuffle($VoteModel); + // $this->election->addVote( $VoteModel ); + // } + + // \var_dump($j); + + // \var_dump($this->election->getVotesListAsString()); + + // \var_dump($this->election->getResult('Ranked Pairs Winning')->getResultAsArray(true)); + + // self::assertEquals(true,true); + // } + // } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/STV/CPO_StvTest.php b/include/Condorcet/Tests/src/Algo/Methods/STV/CPO_StvTest.php new file mode 100644 index 0000000..0d288ad --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/STV/CPO_StvTest.php @@ -0,0 +1,319 @@ +election = new Election; + } + + protected function tearDown(): void + { + $this->election->setMethodOption('STV', 'Quota', StvQuotas::DROOP); + $this->election->setMethodOption('CPO STV', 'Quota', StvQuotas::HAGENBACH_BISCHOFF); + $this->election->setMethodOption('CPO STV', 'CondorcetCompletionMethod', CPO_STV::DEFAULT_METHODS_CHAINING); + $this->election->setMethodOption('CPO STV', 'TieBreakerMethods', CPO_STV::DEFAULT_METHODS_CHAINING); + } + + # From https://en.wikipedia.org/wiki/CPO-STV + public function testCPO1(): void + { + $this->election->setStatsVerbosity(StatsVerbosity::FULL); + + $this->election->addCandidate('Andrea'); // key 0 + $this->election->addCandidate('Brad'); // key 1 + $this->election->addCandidate('Carter'); // key 2 + $this->election->addCandidate('Delilah'); // key 3 + $this->election->addCandidate('Scott'); // key 4 + + $this->election->setImplicitRanking(false); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + Andrea ^25 + Carter > Brad > Delilah ^34 + Brad > Delilah ^7 + Delilah > Brad ^8 + Delilah > Scott ^5 + Scott > Delilah ^21 + '); + + $this->election->setNumberOfSeats(3); + + self::assertSame( + [ + 1 => 'Carter', + 2 => 'Andrea', + 3 => 'Delilah', + ], + $this->election->getResult('CPO STV')->getResultAsArray(true) + ); + + $stats = $this->election->getResult('CPO STV')->getStats(); + + self::assertSame(25.0, $stats['Votes Needed to Win']); + self::assertSame(['Andrea'=> 25.0, + 'Brad'=> 7.0, + 'Carter'=> 34.0, + 'Delilah'=> 13.0, + 'Scott'=> 21.0, + ], $stats['Initial Score Table']); + + self::assertSame(['Andrea', 'Carter'], $stats['Candidates elected from first round']); + self::assertSame(['Brad', 'Delilah', 'Scott'], $stats['Candidates eliminated from first round']); + + self::assertSame([ + ['Andrea', 'Carter', 'Scott'], + ['Andrea', 'Carter', 'Delilah'], + ['Andrea', 'Brad', 'Carter'], + ], $stats['Outcomes']); + + self::assertSame('Schulze Margin', $stats['Completion Method']); + + self::assertSame( + ['Outcome N° 0 compared to Outcome N° 1' => [ + 'candidates_excluded' => [ + 0 => 'Brad', + ], + 'scores_after_exclusion' => [ + 'Andrea' => 25.0, + 'Carter' => 34.0, + 'Delilah' => 20.0, + 'Scott' => 21.0, + ], + 'scores_after_surplus' => [ + 'Andrea' => 25.0, + 'Carter' => 25.0, + 'Delilah' => 29.0, + 'Scott' => 21.0, + ], + 'outcomes_scores' => [ + 0 => 71.0, + 1 => 79.0, + ], + ], + 'Outcome N° 0 compared to Outcome N° 2' => [ + 'candidates_excluded' => [ + 0 => 'Delilah', + ], + 'scores_after_exclusion' => [ + 'Andrea' => 25.0, + 'Brad' => 15.0, + 'Carter' => 34.0, + 'Scott' => 26.0, + ], + 'scores_after_surplus' => [ + 'Andrea' => 25.0, + 'Brad' => 24.0, + 'Carter' => 25.0, + 'Scott' => 26.0, + ], + 'outcomes_scores' => [ + 0 => 76.0, + 2 => 74.0, + ], + ], + 'Outcome N° 1 compared to Outcome N° 2' => [ + 'candidates_excluded' => [ + 0 => 'Scott', + ], + 'scores_after_exclusion' => [ + 'Andrea' => 25.0, + 'Brad' => 7.0, + 'Carter' => 34.0, + 'Delilah' => 34.0, + ], + 'scores_after_surplus' => [ + 'Andrea' => 25.0, + 'Brad' => 16.0, + 'Carter' => 25.0, + 'Delilah' => 34.0, + ], + 'outcomes_scores' => [ + 1 => 84.0, + 2 => 66.0, + ], + ], + ], + $stats['Outcomes Comparison'] + ); + + self::assertArrayHasKey('Condorcet Completion Method Stats', $stats); + } + + + # From https://electowiki.org/wiki/CPO-STV + public function testCPO2(): void + { + // $this->election->setStatsVerbosity(StatsVerbosity::FULL); + $this->election->allowsVoteWeight(true); + + $file = new \SplTempFileObject(-1); + $file->fwrite(<<<'CVOTES' + #/Number of Seats: 3 + Escher ^ 100 + Andre>Nader>Gore ^ 110 + Nader>Gore ^ 18 + Gore>Nader ^ 21 + Gore>Bush ^ 6 + Bush>Gore ^ 45 + CVOTES); + + $cef = new CondorcetElectionFormat($file); + + $cef->setDataToAnElection($this->election); + + $this->election->setMethodOption('CPO-STV', 'Quota', StvQuotas::HARE); + + self::assertSame('Andre > Escher > Gore', $this->election->getResult('CPO STV')->getResultAsString()); + + self::assertSame((float) 100, $this->election->getResult('CPO STV')->getStats()['Votes Needed to Win']); + } + + # From https://electowiki.org/wiki/CPO-STV + public function testCPO3(): void + { + $this->election->setStatsVerbosity(StatsVerbosity::FULL); + $this->election->allowsVoteWeight(true); + + $file = new \SplTempFileObject(-1); + $file->fwrite(<<<'CVOTES' + #/Number of Seats: 2 + A>B>C>D * 5 + A>C>B>D * 17 + D * 8 + CVOTES); + + $cef = new CondorcetElectionFormat($file); + + $cef->setDataToAnElection($this->election); + + $this->election->setMethodOption('CPO-STV', 'Quota', StvQuotas::DROOP); + + self::assertSame('A > C', $this->election->getResult('CPO STV')->getResultAsString()); + + self::assertSame((float) 11, $this->election->getResult('CPO STV')->getStats()['Votes Needed to Win']); + self::assertSame([0=>19.0, 2=>22.0], $this->election->getResult('CPO STV')->getStats()['Outcomes Comparison']['Outcome N° 0 compared to Outcome N° 2']['outcomes_scores']); + self::assertSame([0=>19.0, 1=>22.0], $this->election->getResult('CPO STV')->getStats()['Outcomes Comparison']['Outcome N° 0 compared to Outcome N° 1']['outcomes_scores']); + self::assertSame([1=>19.5, 2=>13.5], $this->election->getResult('CPO STV')->getStats()['Outcomes Comparison']['Outcome N° 1 compared to Outcome N° 2']['outcomes_scores']); + } + + public function testLessOrEqualCandidatesThanSeats(): void + { + $expectedRanking = [ + 1 => 'Memphis', + 2 => 'Nashville', + 3 => 'Chattanooga', + 4 => 'Knoxville', + ]; + + // Ref + $this->election->setNumberOfSeats(4); + + $this->election->addCandidate('Memphis'); + $this->election->addCandidate('Nashville'); + $this->election->addCandidate('Knoxville'); + $this->election->addCandidate('Chattanooga'); + + $this->election->parseVotes(' Memphis * 4 + Nashville * 3 + Chattanooga * 2 + Knoxville * 1'); + + self::assertSame($expectedRanking, $this->election->getResult('CPO STV')->getResultAsArray(true)); + + $this->election->setNumberOfSeats(5); + + self::assertSame($expectedRanking, $this->election->getResult('CPO STV')->getResultAsArray(true)); + } + + public function testEquality1(): void + { + $this->election->setNumberOfSeats(2); + + $this->election->parseCandidates('A;B;C'); + + $this->election->addVote('A>B>C'); + $this->election->addVote('B>A>C'); + $this->election->addVote('B>C>A'); + $this->election->addVote('A>B>C'); + + self::assertSame([1=>['A', 'B']], $this->election->getResult('CPO STV')->getResultAsArray(true)); + + $this->election->setNumberOfSeats(3); + + self::assertSame([1=>['A', 'B'], 3=> 'C'], $this->election->getResult('CPO STV')->getResultAsArray(true)); + } + + public function testEquality2(): void + { + $this->election->setImplicitRanking(false); + $this->election->setNumberOfSeats(3); + + $this->election->parseCandidates('A;B;C;D'); + + $this->election->addVote('A>B>C>D'); + $this->election->addVote('A>B>D>C'); + + self::assertSame([1=>'A', 2=>['B', 'D']], $this->election->getResult('CPO STV')->getResultAsArray(true)); + } + + public function testLimit1(): void + { + $this->expectException(MethodLimitReachedException::class); + $this->expectExceptionMessage('CPO-STV is currently limited to 12000 comparisons in order to avoid unreasonable deadlocks due to non-polyminial runtime aspects of the algorithm. Consult the manual to increase or remove this limit.'); + + + $this->election->setNumberOfSeats(10); + $this->election->parseCandidates('1;2;3;4;5;6;7;8;9;10;11;12;13;14;15'); + $this->election->addVote('1>2'); + + $this->election->getResult('CPO STV'); + } + + public function testCPO40Candidates(): void + { + $this->expectException(MethodLimitReachedException::class); + $this->expectExceptionMessage('CPO-STV is currently limited to 12000 comparisons in order to avoid unreasonable deadlocks due to non-polyminial runtime aspects of the algorithm. Consult the manual to increase or remove this limit.'); + + $this->election->setImplicitRanking(false); + $this->election->setNumberOfSeats((int) (40 / 3)); + + $candidates = []; + for ($i=0; $i < 40; $i++) { + $candidates[] = $this->election->addCandidate(); + } + + if (version_compare(\PHP_VERSION, '8.2') >= 0) { + $randomizer = new \Random\Randomizer(new \Random\Engine\Xoshiro256StarStar('CondorcetReproductibleRandomSeed')); + + $shuffle = static fn (array $candidates): array => $randomizer->shuffleArray($candidates); + } else { + $shuffle = static function (array $candidates): array { + $newCandidates = $candidates; + shuffle($newCandidates); + return $newCandidates; + }; + } + + for ($i = 0; $i < 100; $i++) { + $this->election->addVote($shuffle($candidates)); + } + + $this->election->getResult('CPO STV')->getResultAsString(); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/STV/SingleTransferableVoteTest.php b/include/Condorcet/Tests/src/Algo/Methods/STV/SingleTransferableVoteTest.php new file mode 100644 index 0000000..cdcef72 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/STV/SingleTransferableVoteTest.php @@ -0,0 +1,477 @@ +election = new Election; + } + + protected function tearDown(): void + { + $this->election->setMethodOption('STV', 'Quota', StvQuotas::DROOP); + } + + public function testQuotaOption(): never + { + self::assertSame(StvQuotas::DROOP, StvQuotas::make('droop')); + + self::assertTrue( + $this->election->setMethodOption('STV', 'Quota', StvQuotas::make('Hagenbach-Bischoff')) + ); + + $this->expectException(StvQuotaNotImplementedException::class); + $this->expectExceptionMessage('This STV quota is not implemented: "another quota"'); + + $this->election->setMethodOption('STV', 'Quota', StvQuotas::make('another quota')); + } + + public function testResult_1(): void + { + # From https://fr.wikipedia.org/wiki/Scrutin_%C3%A0_vote_unique_transf%C3%A9rable + + $this->election->addCandidate('D'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('A'); + + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + A>B>C>D ^ 28 + A>C>D>B ^ 14 + B>C>A>D ^ 15 + C>A>B>D ^ 17 + D>B>C>A ^ 26 + '); + + $this->election->setNumberOfSeats(2); + + + self::assertEqualsWithDelta( + [ + 1 => [ + 'A' => 42.0, + 'D' => 26.0, + 'C' => 17.0, + 'B' => 15.0, + ], + 2 => [ + 'D' => 26.0, + 'B' => 20.33333333333, + 'C' => 19.66666666667, + ], + 3 => [ + 'B' => 37.33333333333, + 'D' => 28.66666666667, + ], + ], + $this->election->getResult('STV')->getStats()['rounds'], + 1 / (0.1 ** SingleTransferableVote::DECIMAL_PRECISION) + ); + + self::assertSame( + (float) 34, + $this->election->getResult('STV')->getStats()['Votes Needed to Win'] + ); + + self::assertSame( + [ + 1 => 'A', + 2 => 'B', + ], + $this->election->getResult('STV')->getResultAsArray(true) + ); + } + + public function testResult_2(): void + { + # From https://en.wikipedia.org/wiki/Single_transferable_vote + + $this->election->addCandidate('Orange'); + $this->election->addCandidate('Pear'); + $this->election->addCandidate('Chocolate'); + $this->election->addCandidate('Strawberry'); + $this->election->addCandidate('Hamburger'); + + $this->election->setImplicitRanking(false); + $this->election->allowsVoteWeight(true); + + $this->election->setNumberOfSeats(3); + + + $this->election->parseVotes(' + Orange ^ 4 + Pear > Orange * 2 + Chocolate > Strawberry * 8 + Chocolate > Hamburger * 4 + Strawberry + Hamburger + '); + + self::assertSame( + (float) 6, + $this->election->getResult('STV')->getStats()['Votes Needed to Win'] + ); + + self::assertSame( + [ + 1 => [ + 'Chocolate' => 12.0, + 'Orange' => 4.0, + 'Pear' => 2.0, + 'Strawberry' => 1.0, + 'Hamburger' => 1.0, + ], + 2 => [ + 'Strawberry' => 5.0, + 'Orange' => 4.0, + 'Hamburger' => 3.0, + 'Pear' => 2.0, + ], + 3 => [ + 'Orange' => 6.0, + 'Strawberry' => 5.0, + 'Hamburger' => 3.0, + ], + 4 => [ + 'Strawberry' => 5.0, + 'Hamburger' => 3.0, + ], + 5 => [ + 'Strawberry' => 5.0, + ], + ], + $this->election->getResult('STV')->getStats()['rounds'] + ); + + self::assertSame( + [ + 1 => 'Chocolate', + 2 => 'Orange', + 3 => 'Strawberry', + ], + $this->election->getResult('STV')->getResultAsArray(true) + ); + } + + public function testResult_3(): void + { + # From https://en.wikipedia.org/wiki/Schulze_STV + + $this->election->addCandidate('Andrea'); + $this->election->addCandidate('Brad'); + $this->election->addCandidate('Carter'); + + $this->election->setImplicitRanking(false); + $this->election->allowsVoteWeight(true); + + $this->election->setNumberOfSeats(2); + + $this->election->parseVotes(' + Andrea > Brad > Carter ^ 12 + Andrea > Carter > Brad ^ 26 + Andrea > Carter > Brad ^ 12 + Carter > Andrea > Brad ^ 13 + Brad ^ 27 + '); + + self::assertSame( + (float) 31, + $this->election->getResult('STV')->getStats()['Votes Needed to Win'] + ); + + self::assertSame( + [ + 1 => [ + 'Andrea' => 50.0, + 'Brad' => 27.0, + 'Carter' => 13.0, + ], + 2 => [ + 'Brad' => 31.56, + 'Carter' => 27.44, + ], + ], + $this->election->getResult('STV')->getStats()['rounds'] + ); + + self::assertSame( + [ + 1 => 'Andrea', + 2 => 'Brad', + ], + $this->election->getResult('STV')->getResultAsArray(true) + ); + + $this->election->setStatsVerbosity(StatsVerbosity::LOW); + self::assertArrayNotHasKey('rounds', $this->election->getResult('STV')->getStats()); + } + + public function testResult_4(): void + { + # From https://it.wikipedia.org/wiki/Voto_singolo_trasferibile + + $this->election->addCandidate('D'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('A'); + + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + A>D ^ 40 + B>A ^ 10 + B>C ^ 5 + C>B ^ 25 + D>B ^ 20 + '); + + $this->election->setNumberOfSeats(3); + + self::assertSame( + (float) 26, + $this->election->getResult('STV')->getStats()['Votes Needed to Win'] + ); + + self::assertSame( + [ + 1 => 'A', + 2 => 'D', + 3 => 'C', + ], + $this->election->getResult('STV')->getResultAsArray(true) + ); + } + + + public function testResult_AlternativeQuotas1(): void + { + # From https://en.wikipedia.org/wiki/Hagenbach-Bischoff_quota + + $this->election->addCandidate('Andrea'); + $this->election->addCandidate('Carter'); + $this->election->addCandidate('Brad'); + + $this->election->setImplicitRanking(false); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + Andrea > Carter ^45 + Carter ^25 + Brad ^30 + '); + + $this->election->setNumberOfSeats(2); + $this->election->setMethodOption('STV', 'Quota', StvQuotas::make('Hagenbach-Bischoff')); + + self::assertSame( + round(33 + 1/3, SingleTransferableVote::DECIMAL_PRECISION, \PHP_ROUND_HALF_DOWN), + $this->election->getResult('STV')->getStats()['Votes Needed to Win'] + ); + + self::assertEqualsWithDelta( + [ + 1 => [ + 'Andrea' => 45.0, + 'Brad' => 30.0, + 'Carter' => 25.0, + ], + 2 => [ + 'Carter' => 36.0 + 2/3, + 'Brad' => 30.0, + ], + ], + $this->election->getResult('STV')->getStats()['rounds'], + delta: 1 / (0.1 ** SingleTransferableVote::DECIMAL_PRECISION) + ); + + self::assertSame( + [ + 1 => 'Andrea', + 2 => 'Carter', + ], + $this->election->getResult('STV')->getResultAsArray(true) + ); + + self::assertsame($this->election->getResult('STV')->getMethodOptions()['Quota'], StvQuotas::make('Hagenbach-Bischoff')); + } + + public function testResult_AlternativeQuotas2(): void + { + # From https://en.wikipedia.org/wiki/Imperiali_quota + + $this->election->addCandidate('Andrea'); + $this->election->addCandidate('Carter'); + $this->election->addCandidate('Brad'); + + $this->election->setImplicitRanking(false); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + Andrea > Carter ^65 + Carter ^15 + Brad ^20 + '); + + $this->election->setNumberOfSeats(2); + $this->election->setMethodOption('STV', 'Quota', StvQuotas::IMPERIALI); + + self::assertSame( + (float) (100 / (2 + 2)), + $this->election->getResult('STV')->getStats()['Votes Needed to Win'] + ); + + self::assertSame( + [ + 1 => [ + 'Andrea' => 65.0, + 'Brad' => 20.0, + 'Carter' => 15.0, + ], + 2 => [ + 'Carter' => 55.0, + 'Brad' => 20.0, + ], + ], + $this->election->getResult('STV')->getStats()['rounds'] + ); + + self::assertSame( + [ + 1 => 'Andrea', + 2 => 'Carter', + ], + $this->election->getResult('STV')->getResultAsArray(true) + ); + + self::assertsame($this->election->getResult('STV')->getMethodOptions()['Quota'], StvQuotas::make('Imperiali quota')); + } + + public function testResult_AlternativeQuotas3(): void + { + # From https://en.wikipedia.org/wiki/Hare_quota + + $this->election->addCandidate('Andrea'); + $this->election->addCandidate('Carter'); + $this->election->addCandidate('Brad'); + + $this->election->setImplicitRanking(false); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + Andrea > Carter ^60 + Carter ^14 + Brad ^26 + '); + + $this->election->setNumberOfSeats(2); + $this->election->setMethodOption('STV', 'Quota', StvQuotas::make('Hare quota')); + + self::assertSame( + (float) (100 / 2), + $this->election->getResult('STV')->getStats()['Votes Needed to Win'] + ); + + self::assertSame( + [ + 1 => [ + 'Andrea' => 60.0, + 'Brad' => 26.0, + 'Carter' => 14.0, + ], + 2 => [ + 'Brad' => 26.0, + 'Carter' => 24.0, + ], + 3 => ['Brad' => 26.0], + ], + $this->election->getResult('STV')->getStats()['rounds'] + ); + + self::assertSame( + [ + 1 => 'Andrea', + 2 => 'Brad', + ], + $this->election->getResult('STV')->getResultAsArray(true) + ); + + self::assertsame($this->election->getResult('STV')->getMethodOptions()['Quota'], StvQuotas::HARE); + } + + public function testResult_AlternativeQuotas4(): void + { + # From https://en.wikipedia.org/wiki/CPO-STV + + $this->election->addCandidate('Andrea'); + $this->election->addCandidate('Carter'); + $this->election->addCandidate('Brad'); + $this->election->addCandidate('Delilah'); + $this->election->addCandidate('Scott'); + + $this->election->setImplicitRanking(false); + $this->election->allowsVoteWeight(true); + + $this->election->parseVotes(' + Andrea ^25 + Carter > Brad > Delilah ^34 + Brad > Delilah ^7 + Delilah > Brad ^8 + Delilah > Scott ^5 + Scott > Delilah ^21 + '); + + $this->election->setNumberOfSeats(3); + $this->election->setMethodOption('STV', 'Quota', StvQuotas::HAGENBACH_BISCHOFF); + + self::assertSame( + (float) 25, + $this->election->getResult('STV')->getStats()['Votes Needed to Win'] + ); + + self::assertSame( + [ + 1 => [ + 'Carter' => 34.0, + 'Andrea' => 25.0, + 'Scott' => 21.0, + 'Delilah' => 13.0, + 'Brad' => 7.0, + ], + 2 => [ + 'Scott' => 21.0, + 'Brad' => 16.0, + 'Delilah' => 13.0, + ], + 3 => [ + 'Scott' => 26.0, + 'Brad' => 24.0, + ], + ], + $this->election->getResult('STV')->getStats()['rounds'] + ); + + self::assertSame( + [ + 1 => 'Carter', + 2 => 'Andrea', + 3 => 'Scott', + ], + $this->election->getResult('STV')->getResultAsArray(true) + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Methods/Schulze/SchulzeTest.php b/include/Condorcet/Tests/src/Algo/Methods/Schulze/SchulzeTest.php new file mode 100644 index 0000000..4b21197 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Methods/Schulze/SchulzeTest.php @@ -0,0 +1,367 @@ +election = new Election; + } + + public function testResult_1(): void + { + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + $this->election->addCandidate('D'); + $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A > C > B > E * 5 + A > D > E > C * 5 + B > E > D > A * 8 + C > A > B > E * 3 + C > A > E > B * 7 + C > B > A > D * 2 + D > C > E > B * 7 + E > B > A > D * 8 + '); + + self::assertEquals('E', $this->election->getWinner('Schulze Winning')); + + self::assertSame( + [1 => 'E', + 2 => 'A', + 3 => 'C', + 4 => 'B', + 5 => 'D', ], + $this->election->getResult('Schulze Winning')->getResultAsArray(true) + ); + } + + public function testResult_2(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A > B > C * 3 + D > A > B * 2 + D > B > C * 2 + C > B > D * 2 + '); + + self::assertSame([$candidateB, $candidateD], $this->election->getWinner('Schulze Winning')); + + self::assertSame( + [1 => ['B', 'D'], + 2 => ['A', 'C'], ], + $this->election->getResult('Schulze Winning')->getResultAsArray(true) + ); + } + + public function testSchulzeOfficialExampleResult_1(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A > C > D * 8 + B > A > D * 2 + C > D > B * 4 + D > B > A * 4 + D > C > B * 3 + '); + + self::assertSame($candidateD, $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_2(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A > B > C * 3 + C > B > D * 2 + D > A > B * 2 + D > B > C * 2 + '); + + self::assertSame([$candidateB, $candidateD], $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_3(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A > B > C * 12 + A > D > B * 6 + B > C > D * 9 + C > D > A * 15 + D > B > A * 21 + '); + + self::assertSame($candidateD, $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_4(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A > C > D * 6 + B > A > D * 1 + C > B > D * 3 + D > B > A * 3 + D > C > B * 2 + '); + + self::assertSame([$candidateA, $candidateD], $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_5(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + $candidateE = $this->election->addCandidate('E'); + $candidateF = $this->election->addCandidate('F'); + + $this->election->parseVotes(' + A > D > E > B > C * 3 + B > F > E > C > D * 3 + C > A > B > F > D * 4 + D > B > C > E > F * 1 + D > E > F > A > B * 4 + E > C > B > D > F * 2 + F > A > C > D > B * 2 + '); + + # Situation 1 + self::assertSame($candidateA, $this->election->getWinner('Schulze Winning')); + + # Situation 2 + $this->election->parseVotes('A > E > F > C > B * 2'); + + self::assertSame($candidateD, $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_6_situation_1(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A > B > D * 3 + A > D > B * 5 + A > D > C * 1 + B > A > D * 2 + B > D > C * 2 + C > A > B * 4 + C > B > A * 6 + D > B > C * 2 + D > C > A * 5 + '); + + self::assertSame($candidateA, $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_6_situation_2(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + $candidateE = $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A > B > D > E * 3 + A > D > E > B * 5 + A > D > E > C * 1 + B > A > D > E * 2 + B > D > E > C * 2 + C > A > B > D * 4 + C > B > A > D * 6 + D > B > E > C * 2 + D > E > C > A * 5 + '); + + self::assertSame($candidateB, $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_7(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + + $this->election->parseVotes(' + A > B > C > D * 6 + A = B * 8 + A = C * 8 + A = C > D * 18 + A = C = D * 8 + B * 40 + C > B > D * 4 + C > D > A * 9 + C = D * 8 + D > A > B * 14 + D > B > C * 11 + D > C > A * 4 + '); + + # Margin + self::assertSame($candidateA, $this->election->getWinner('Schulze Margin')); + + # Ratio + self::assertSame($candidateB, $this->election->getWinner('Schulze Ratio')); + + # Winning + self::assertSame($candidateD, $this->election->getWinner('Schulze Winning')); + + # Losing Votes + // not implemented + } + + public function testSchulzeOfficialExampleResult_8(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + $candidateE = $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A > D > B > E * 9 + B > C > A > D * 6 + B > C > D > E * 5 + C > D > B > E * 2 + D > E > C > B * 6 + E > A > C > B * 14 + E > C > A > B * 2 + E > D > A > C * 1 + '); + + self::assertSame($candidateB, $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_9(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + $candidateE = $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A > D > B > E * 9 + B > A > C > E * 1 + C > B > A > D * 6 + C > D > B > E * 2 + C > D > E > A * 5 + D > E > C > A * 6 + E > B > A > C * 14 + E > B > C > A * 2 + '); + + self::assertSame($candidateE, $this->election->getWinner('Schulze Winning')); + } + + public function testSchulzeOfficialExampleResult_10(): void + { + $candidateA = $this->election->addCandidate('A'); + $candidateB = $this->election->addCandidate('B'); + $candidateC = $this->election->addCandidate('C'); + $candidateD = $this->election->addCandidate('D'); + $candidateE = $this->election->addCandidate('E'); + + $this->election->parseVotes(' + A > C > B > E * 5 + A > D > E > C * 5 + B > E > D > A * 8 + C > A > B > E * 3 + C > A > E > B * 7 + C > B > A > D * 2 + D > C > E > B * 7 + E > B > A > D * 8 + '); + + self::assertSame($candidateE, $this->election->getWinner('Schulze Winning')); + } + + public function testResult_11(): void + { + $this->election->addCandidate('Abby'); + $this->election->addCandidate('Brad'); + $this->election->addCandidate('Cora'); + $this->election->addCandidate('Dave'); + $this->election->addCandidate('Erin'); + + $this->election->parseVotes(' + Abby>Cora>Erin>Dave>Brad * 98 + Brad>Abby>Erin>Cora>Dave * 64 + Brad>Abby>Erin>Dave>Cora * 12 + Brad>Erin>Abby>Cora>Dave * 98 + Brad>Erin>Abby>Dave>Cora * 13 + Brad>Erin>Dave>Abby>Cora * 125 + Cora>Abby>Erin>Dave>Brad * 124 + Cora>Erin>Abby>Dave>Brad * 76 + Dave>Abby>Brad>Erin>Cora * 21 + Dave>Brad>Abby>Erin>Cora * 30 + Dave>Brad>Erin>Cora>Abby * 98 + Dave>Cora>Abby>Brad>Erin * 139 + Dave>Cora>Brad>Abby>Erin * 23 + '); + + self::assertEquals('Abby', $this->election->getWinner('Schulze Winning')); + + self::assertSame( + [1 => 'Abby', + 2 => 'Brad', + 3 => 'Erin', + 4 => 'Dave', + 5 => 'Cora', ], + $this->election->getResult('Schulze Winning')->getResultAsArray(true) + ); + } + + public function testSchulzeRatioEquality(): void + { + $this->election->parseCandidates('A;B;C;D'); + $this->election->parseVotes('A>B=C>D * 10'); + + self::assertSame( + [1 => 'A', + 2 => ['B', 'C'], + 3 => 'D', + ], + $this->election->getResult('Schulze Ratio')->getResultAsArray(true) + ); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Tools/CombinationsTest.php b/include/Condorcet/Tests/src/Algo/Tools/CombinationsTest.php new file mode 100644 index 0000000..0e21f44 --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Tools/CombinationsTest.php @@ -0,0 +1,76 @@ +expectException(IntegerOverflowException::class); + Combinations::getPossibleCountOfCombinations(78, 24); // Tarot Card Game - 3 players - Result is 79_065_487_387_985_398_300, it's above PHP_MAX_INT + } + + public function testCountPossibleCombinationsResultWithoutBigInt(): void + { + Combinations::$useBigIntegerIfAvailable = false; + + // Usual permutation for CPO STV 11 candidates and 3 seats left + self::assertSame( + 13_530, + Combinations::getPossibleCountOfCombinations( + count: Combinations::getPossibleCountOfCombinations( + count: 11, + length: 3 + ), + length: 2 + ) + ); + + self::assertSame(2_598_960, Combinations::getPossibleCountOfCombinations(52, 5)); // Card Game - Result is - 4_367_914_309_753_280 + + $this->expectException(IntegerOverflowException::class); + Combinations::getPossibleCountOfCombinations(78, 15); // Tarot Card Game - 5 players - - Result is 4_367_914_309_753_280, it's NOT above PHP_MAX_INT but the intermediate calculations are. + } + + public function testCountPossibleCombinationsBadParameters1(): void + { + $this->expectException(CondorcetInternalException::class); + + Combinations::getPossibleCountOfCombinations(2, 3); + } + + public function testIntegerOverflow(): void + { + $this->expectException(IntegerOverflowException::class); + + Combinations::getPossibleCountOfCombinations(\PHP_INT_MAX - 1, 2); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Tools/PermutationsTest.php b/include/Condorcet/Tests/src/Algo/Tools/PermutationsTest.php new file mode 100644 index 0000000..3eae4fa --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Tools/PermutationsTest.php @@ -0,0 +1,75 @@ +expectException(CondorcetInternalException::class); + Permutations::getPossibleCountOfPermutations(0); + } + + public function testIntegerOverflowWithBigInt(): void + { + $this->expectException(IntegerOverflowException::class); + Permutations::getPossibleCountOfPermutations(42); + } + + public function testIntegerOverflowWithoutBigInt(): void + { + Permutations::$useBigIntegerIfAvailable = false; + + $this->expectException(IntegerOverflowException::class); + Permutations::getPossibleCountOfPermutations(42); + } + + public function testPermutationsAllResultsFor3(): void + { + $p = new Permutations([0, 1, 2]); + + $r = $p->getResults(); + + self::assertInstanceOf(\SplFixedArray::class, $r); + self::assertSame(6, $r->getSize()); + + self::assertSame( + [[1=>0, 2=>1, 3=>2], + [1=>1, 2=>0, 3=>2], + [1=>1, 2=>2, 3=>0], + [1=>0, 2=>2, 3=>1], + [1=>2, 2=>0, 3=>1], + [1=>2, 2=>1, 3=>0], + ], + $r->toArray() + ); + } + + public function testPermutationsAllResultsFor1(): void + { + $p = new Permutations([42]); + + $r = $p->getResults(); + + self::assertInstanceOf(\SplFixedArray::class, $r); + self::assertSame(1, $r->getSize()); + + self::assertSame([[1=>42]], $r->toArray()); + } +} diff --git a/include/Condorcet/Tests/src/Algo/Tools/VirtualVoteTest.php b/include/Condorcet/Tests/src/Algo/Tools/VirtualVoteTest.php new file mode 100644 index 0000000..323a19f --- /dev/null +++ b/include/Condorcet/Tests/src/Algo/Tools/VirtualVoteTest.php @@ -0,0 +1,30 @@ +parseCandidates('A;B;C'); + + $vote1 = new Vote('A>B>C'); + $election->addVote($vote1); + + $vote2 = VirtualVote::removeCandidates($vote1, ['B']); + + self::assertNotSame($vote1->getSimpleRanking(), $vote2->getSimpleRanking()); + self::assertSame('A > C', $vote2->getSimpleRanking()); + + self::assertSame(1, $vote1->countLinks()); + self::assertSame(0, $vote2->countLinks()); + self::assertSame(1, $election->countVotes()); + } +} diff --git a/include/Condorcet/Tests/src/CandidateTest.php b/include/Condorcet/Tests/src/CandidateTest.php new file mode 100644 index 0000000..930359c --- /dev/null +++ b/include/Condorcet/Tests/src/CandidateTest.php @@ -0,0 +1,176 @@ +candidate1 = new Candidate('candidate1.n1'); + } + + public function testCreateTimestamp(): void + { + self::assertEquals($this->candidate1->getCreateTimestamp(), $this->candidate1->getTimestamp()); + } + + public function testChangeName(): void + { + self::assertTrue($this->candidate1->setName('candidate1.n2')); + + self::assertEquals('candidate1.n2', $this->candidate1->getName()); + + self::assertLessThan($this->candidate1->getTimestamp(), $this->candidate1->getCreateTimestamp()); + self::assertCount(2, $this->candidate1->getHistory()); + } + + public function testTrimName(): void + { + $candidate = new Candidate(' candidateName '); + self::assertSame('candidateName', (string) $candidate); + } + + public function testMatchingAndTooLongName(): never + { + $name = ''; + while (mb_strlen($name) < Election::MAX_CANDIDATE_NAME_LENGTH) { + $name .= uniqid(); + } + $name = mb_substr($name, 0, Election::MAX_CANDIDATE_NAME_LENGTH); + + // The name is exactly as long as allowed. + $candidate = new Candidate($name); + $this->assertEquals($name, (string) $candidate); + + // Now the name is one character too long. + $name .= 'A'; + + $this->expectException(CandidateInvalidNameException::class); + $this->expectExceptionMessage("This name is not valid: {$name}"); + + new Candidate($name); + } + + public function testBadName(): never + { + $this->expectException(CandidateInvalidNameException::class); + $this->expectExceptionMessage('This name is not valid'); + + new Candidate('<$"'); + } + + public function testBadNameWithNewline(): never + { + $this->expectException(CandidateInvalidNameException::class); + $this->expectExceptionMessage('This name is not valid'); + + new Candidate("A name with\n a newline"); + } + + public function testCandidateBadClass(): never + { + $this->expectException(\TypeError::class); + + (new Election)->addCandidate(new \stdClass); + } + + public function testAddSameCandidate1(): never + { + $this->expectException(CandidateExistsException::class); + $this->expectExceptionMessage('This candidate already exists: Schizophrenic'); + + $election1 = new Election; + + $candidate = new Candidate('Schizophrenic'); + + $election1->addCandidate($candidate); + $election1->addCandidate($candidate); + } + + public function testAddSameCandidate2(): never + { + $this->expectException(CandidateExistsException::class); + $this->expectExceptionMessage('This candidate already exists: candidate1'); + + $election1 = new Election; + + $election1->parseCandidates('candidate1;candidate2;candidate1'); + } + + public function testAddSameCandidate3(): never + { + $this->expectException(CandidateExistsException::class); + $this->expectExceptionMessage('This candidate already exists: candidate1'); + + $election1 = new Election; + + $election1->addCandidate('candidate1'); + $election1->parseCandidates('candidate2;candidate1'); + } + + public function testAddSameCandidate4(): void + { + $election1 = new Election; + + $candidate1= $election1->addCandidate('candidate1'); + + try { + $election1->parseCandidates('candidate2;candidate1'); + } catch (\Exception) { + } + + self::assertsame([$candidate1], $election1->getCandidatesList()); + } + + public function testSameCandidateToMultipleElection(): void + { + $this->expectException(CandidateExistsException::class); + $this->expectExceptionMessage("This candidate already exists: the name 'Debussy' is taken by another candidate"); + + $election1 = new Election; + $election2 = new Election; + $election3 = new Election; + + // Add candidate to election + self::assertSame($this->candidate1, $election1->addCandidate($this->candidate1)); + self::assertSame($this->candidate1, $election2->addCandidate($this->candidate1)); + self::assertSame($this->candidate1, $election3->addCandidate($this->candidate1)); + + // Check Candidate Link + self::assertTrue($this->candidate1->haveLink($election1)); + self::assertTrue($this->candidate1->haveLink($election2)); + self::assertTrue($this->candidate1->haveLink($election3)); + self::assertCount(3, $this->candidate1->getLinks()); + + $election3->removeCandidates('candidate1.n1'); + + self::assertCount(2, $this->candidate1->getLinks()); + + // Add some conflicts + self::assertTrue($this->candidate1->setName('candidate1.n2')); + self::assertSame('candidate1.n2', $this->candidate1->getName()); + self::assertNotSame($this->candidate1, $election1->addCandidate('candidate1.n1')); + + $election2->addCandidate('Debussy'); + $this->candidate1->setName('Debussy'); + } + + public function testCloneCandidate(): void + { + ($election = new Election)->addCandidate($this->candidate1); + + self::assertsame(1, $this->candidate1->countLinks()); + + $cloneCandidate = clone $this->candidate1; + + self::assertsame(0, $cloneCandidate->countLinks()); + } +} diff --git a/include/Condorcet/Tests/src/CondorcetTest.php b/include/Condorcet/Tests/src/CondorcetTest.php new file mode 100644 index 0000000..eaca612 --- /dev/null +++ b/include/Condorcet/Tests/src/CondorcetTest.php @@ -0,0 +1,204 @@ +expectException(AlgorithmException::class); + $this->expectExceptionMessage("The voting algorithm is not available: no class found for 'sjskkdlkkzksh'"); + + Condorcet::addMethod('sjskkdlkkzksh'); + } + + public function testAuthMethod(): void + { + self::assertFalse(Condorcet::isAuthMethod('skzljdpmzk')); + self::assertNull(Condorcet::getMethodClass('skzljdpmzk')); + self::assertSame(\CondorcetPHP\Condorcet\Algo\Methods\Schulze\SchulzeWinning::class, Condorcet::getMethodClass('Schulze Winning')); + } + + /** + * @preserveGlobalState disabled + * @backupStaticAttributes disabled + * @runInSeparateProcess + */ + public function testAddMethod(): never + { + $this->expectException(AlgorithmException::class); + $this->expectExceptionMessage('The voting algorithm is not available: the given class is using an existing alias'); + + $algoClassPath = CondorcetTest_ValidAlgorithmName::class; + + self::assertTrue(Condorcet::addMethod($algoClassPath)); + + self::assertEquals($algoClassPath, Condorcet::getMethodClass($algoClassPath)); + + // Try to add existing alias + $algoClassPath = CondorcetTest_DuplicateAlgorithmAlias::class; + + self::assertFalse(Condorcet::addMethod($algoClassPath)); + } + + public function testAddUnvalidMethod(): never + { + $this->expectException(AlgorithmException::class); + $this->expectExceptionMessage('The voting algorithm is not available: the given class is not correct'); + + $algoClassPath = CondorcetTest_UnvalidAlgorithmName::class; + + self::assertFalse(Condorcet::addMethod($algoClassPath)); + + self::assertSame( + CondorcetTest_UnvalidAlgorithmName::class, + Condorcet::getMethodClass('FirstMethodName') + ); + } + + public function testUnvalidDefaultMethod(): void + { + self::assertFalse(Condorcet::setDefaultMethod('dgfbdwcd')); + } + + public function testEmptyMethod(): never + { + $this->expectException(AlgorithmException::class); + $this->expectExceptionMessage('The voting algorithm is not available: no method name given'); + + Condorcet::isAuthMethod(''); + } + + public function testMethodAlias(): void + { + self::assertSame( + \CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung::class, + Condorcet::getMethodClass('kemeny–Young') + ); + + self::assertSame( + \CondorcetPHP\Condorcet\Algo\Methods\KemenyYoung\KemenyYoung::class, + Condorcet::getMethodClass('Maximum likelihood Method') + ); + } +} + + +class CondorcetTest_ValidAlgorithmName extends Method implements MethodInterface +{ + public const METHOD_NAME = ['FirstMethodName', 'Alias1', 'Alias_2', 'Alias 3']; + + + // Get the Result object + public function getResult($options = null): Result + { + // Cache + if ($this->Result !== null) { + return $this->Result; + } + + ////// + + // Ranking calculation + $this->makeRanking(); + + // Return + return $this->Result; + } + + + // Compute the Stats + protected function getStats(): array + { + return []; // You are free to do all you wants. Must be an array.; + } + + + + /////////// COMPUTE /////////// + + + //:: ALGORITHM. ::// + + protected function makeRanking(): void + { + $this->selfElection->get()->getPairwise(); + + $result = [0=>1, 1=>2, 2=>3]; // Candidate must be valid candidates + + $this->Result = $this->createResult($result); + } +} + +class CondorcetTest_DuplicateAlgorithmAlias extends CondorcetTest_ValidAlgorithmName implements MethodInterface +{ + public const METHOD_NAME = ['SecondMethodName', 'Alias_2']; +} + + +class CondorcetTest_UnvalidAlgorithmName +{ + public const METHOD_NAME = ['FirstMethodName', 'Alias1', 'Alias_2', 'Alias 3']; + + + // Get the Result object + public function getResult($options = null): Result + { + // Cache + if ($thisResult !== null) { + return $this->Result; + } + + // Ranking calculation + $this->makeRanking(); + + // Return + return $this->Result; + } + + + // Compute the Stats + protected function getStats(): array + { + return []; // You are free to do all you wants. Must be an array.; + } + + + + /////////// COMPUTE /////////// + + + //:: ALGORITHM. ::// + + protected function makeRanking(): void + { + $this->selfElection->getPairwise(); + + $result = [0=>0, 1=> [1, 2], 2=> 3]; // Candidate must be valid internal candidate key. + + $this->Result = $result; + } +} diff --git a/include/Condorcet/Tests/src/CondorcetUtilTest.php b/include/Condorcet/Tests/src/CondorcetUtilTest.php new file mode 100644 index 0000000..bc5938f --- /dev/null +++ b/include/Condorcet/Tests/src/CondorcetUtilTest.php @@ -0,0 +1,26 @@ +B>C'); + + $this->assertSame('A > B > C', CondorcetUtil::format($vote, true)); + } + + public function testDeleteComments(): void + { + $result = CondorcetUtil::prepareParse('A > B # This is a comment', false); + + $this->assertSame(['A > B'], $result); + } +} diff --git a/include/Condorcet/Tests/src/CondorcetVersionTest.php b/include/Condorcet/Tests/src/CondorcetVersionTest.php new file mode 100644 index 0000000..5c754d3 --- /dev/null +++ b/include/Condorcet/Tests/src/CondorcetVersionTest.php @@ -0,0 +1,19 @@ +getObjectVersion()); + self::assertSame(CONDORCET::getVersion(true), $election->getObjectVersion(true)); + } +} diff --git a/include/Condorcet/Tests/src/Console/Commands/ElectionCommandTest.php b/include/Condorcet/Tests/src/Console/Commands/ElectionCommandTest.php new file mode 100644 index 0000000..fbfb06f --- /dev/null +++ b/include/Condorcet/Tests/src/Console/Commands/ElectionCommandTest.php @@ -0,0 +1,404 @@ +electionCommand = new CommandTester(CondorcetApplication::$SymfonyConsoleApplication->find('election')); + } + + public function testConsoleSimpleElection(): void + { + $this->electionCommand->execute( + [ + '--candidates' => 'A;B;C', + '--votes' => 'A>B>C;C>B>A;B>A>C', + '--stats' => null, + '--natural-condorcet' => null, + '--allows-votes-weight' => null, + '--no-tie' => null, + '--list-votes' => null, + '--deactivate-implicit-ranking' => null, + '--show-pairwise' => null, + ], + [ + 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, + ] + ); + + $output = $this->electionCommand->getDisplay(); + + self::assertStringContainsString('3 candidates registered || 3 votes registered', $output); + + self::assertStringContainsString('Schulze', $output); + self::assertStringContainsString('Registered candidates', $output); + self::assertStringContainsString('Stats - votes registration', $output); + self::assertStringContainsString('Registered Votes List', $output); + self::assertStringContainsString('Pairwise', $output); + self::assertStringContainsString('Stats:', $output); + + self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+TRUE/', $output); + self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+FALSE./', $output); + self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+FALSE/', $output); + } + + public function testConsoleSeats(): void + { + $this->electionCommand->execute( + [ + '--candidates' => 'A;B;C', + '--votes' => 'A>B>C;C>B>A;B>A>C', + '--stats' => null, + '--natural-condorcet' => null, + '--allows-votes-weight' => null, + '--no-tie' => null, + '--list-votes' => null, + '--deactivate-implicit-ranking' => null, + '--show-pairwise' => null, + + '--seats' => 42, + 'methods' => ['STV'], + ], + [ + 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, + ] + ); + + $output = $this->electionCommand->getDisplay(); + + self::assertStringContainsString('3 candidates registered || 3 votes registered', $output); + + self::assertStringContainsString('Seats:', $output); + self::assertStringContainsString('42', $output); + } + + public function testQuotas(): void + { + $this->electionCommand->execute([ + '--candidates' => 'A;B;C', + '--votes' => 'A>B>C;C>B>A;B>A>C', + + 'methods' => ['STV'], + ]); + + $output = $this->electionCommand->getDisplay(); + + self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+TRUE/', $output); + self::assertStringContainsString('Droop Quota', $output); + + $this->electionCommand->execute([ + '--candidates' => 'A;B;C', + '--votes' => 'A>B>C;C>B>A;B>A>C', + + 'methods' => ['STV'], + '--quota' => 'imperiali', + ]); + + $output = $this->electionCommand->getDisplay(); + + self::assertStringContainsString('Imperiali', $output); + } + + public function testConsoleAllMethodsArgument(): void + { + $this->electionCommand->execute([ + '--candidates' => 'A;B;C', + '--votes' => 'A>B>C;C>B>A;B>A>C', + + 'methods' => ['all'], + ]); + + $output = $this->electionCommand->getDisplay(); + // \var_dump($output); + + self::assertStringContainsString('Copeland', $output); + } + + public function testConsoleMultiplesMethods(): void + { + $this->electionCommand->execute([ + '--candidates' => 'A;B;C', + '--votes' => 'A>B>C;C>B>A;B>A>C', + + 'methods' => ['Copeland', 'RankedPairs', 'Minimax'], + ]); + + $output = $this->electionCommand->getDisplay(); + // \var_dump($output); + + self::assertStringContainsString('Copeland', $output); + self::assertStringContainsString('Ranked Pairs M', $output); + self::assertStringContainsString('Minimax Winning', $output); + } + + public function testConsoleFileInput(): void + { + $this->electionCommand->execute([ + '--candidates' => __DIR__.'/data.candidates', + '--votes' => __DIR__.'/data.votes', + ]); + + $output = $this->electionCommand->getDisplay(); + // \var_dump($output); + + self::assertStringContainsString('Schulze', $output); + self::assertStringContainsString('A,B', $output); + self::assertStringContainsString('C '.CondorcetStyle::CONDORCET_LOSER_SYMBOL, $output); + } + + public function testInteractiveCommand(): void + { + $this->electionCommand->setInputs([ + 'A', + 'B', + 'C', + '', + 'A>B>C', + 'B>A>C', + 'A>C>B', + '', + ]); + + $this->electionCommand->execute([ + 'command' => 'election', + ]); + + $output = $this->electionCommand->getDisplay(); + // \var_dump($output); + + self::assertStringContainsString('Results: Schulze Winning', $output); + } + + public function testNonInteractionMode(): never + { + $this->expectException(ResultRequestedWithoutVotesException::class); + $this->expectExceptionMessage('The result cannot be requested without votes'); + + $this->electionCommand->execute([], ['interactive' => false]); + + // $output = $this->electionCommand->getDisplay(); + // \var_dump($output); + } + + public function testCustomizeVotesPerMb(): void + { + $this->electionCommand->execute([ + '--candidates' => 'A;B;C', + '--votes' => 'A>B>C;C>B>A;B>A>C', + '--votes-per-mb' => 42, + ]); + + self::assertSame(42, \CondorcetPHP\Condorcet\Console\Commands\ElectionCommand::$VotesPerMB); + + // $output = $this->electionCommand->getDisplay(); + // \var_dump($output); + } + + public function testVoteWithDb1(): void + { + ElectionCommand::$forceIniMemoryLimitTo = '128M'; + + $this->electionCommand->execute([ + '--candidates' => 'A;B;C', + '--votes-per-mb' => 1, + '--votes' => 'A>B>C * '.(((int) preg_replace('`[^0-9]`', '', ElectionCommand::$forceIniMemoryLimitTo)) + 1), # Must be superior to memory limit in MB + ], [ + 'verbosity' => OutputInterface::VERBOSITY_DEBUG, + ]); + + $output = $this->electionCommand->getDisplay(); + + self::assertMatchesRegularExpression('/Votes per Mb +1/', $output); + self::assertMatchesRegularExpression('/Db is used +yes, using path\\:/', $output); + + ElectionCommand::$forceIniMemoryLimitTo = null; + + # And absence of this error: unlink(path): Resource temporarily unavailable + } + + + public function testNaturalCondorcet(): void + { + $this->electionCommand->execute([ + '--candidates' => 'A;B;C', + '--votes' => 'A=B=C', + '--natural-condorcet' => true, + ]); + + $output = $this->electionCommand->getDisplay(); + + self::assertStringContainsString(CondorcetStyle::CONDORCET_WINNER_SYMBOL.' Condorcet Winner | -', $output); + self::assertStringContainsString(CondorcetStyle::CONDORCET_LOSER_SYMBOL.' Condorcet Loser | -', $output); + } + + public function testFromCondorcetElectionFormat_DoubleCandidates(): void + { + $this->expectException(CandidateExistsException::class); + + $this->electionCommand->execute( + [ + '--candidates' => 'A;B;C', + '--import-condorcet-election-format' => __DIR__.'/../../Tools/Converters/CondorcetElectionFormatData/test1.cvotes', + ], + [ + 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, + ] + ); + } + + public function testFromCondorcetElectionFormat_ArgumentpriorityAndDoubleVoteArgument(): void + { + $this->electionCommand->execute( + [ + '--import-condorcet-election-format' => __DIR__.'/../../Tools/Converters/CondorcetElectionFormatData/test1.cvotes', + '--votes' => 'C>A', + '--deactivate-implicit-ranking' => null, + '--no-tie' => null, + '--allows-votes-weight' => null, + ], + [ + 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, + ] + ); + + $output = $this->electionCommand->getDisplay(); + + self::assertStringContainsString('3 candidates registered || 2 votes registered', $output); + + self::assertStringContainsString('Schulze', $output); + self::assertStringContainsString('Registered candidates', $output); + self::assertStringContainsString('Stats - votes registration', $output); + + self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+TRUE/', $output); + self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+FALSE./', $output); + self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+FALSE/', $output); + + self::assertStringContainsString('Sum vote weight | 3', $output); + } + + public function testFromCondorcetElectionFormat_Arguments(): void + { + $this->electionCommand->execute( + [ + '--import-condorcet-election-format' => __DIR__.'/../../Tools/Converters/CondorcetElectionFormatData/test2.cvotes', + ], + [ + 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, + ] + ); + + $output = $this->electionCommand->getDisplay(); + + self::assertStringContainsString('3 candidates registered || 2 votes registered', $output); + + self::assertStringContainsString('Schulze', $output); + self::assertStringContainsString('Registered candidates', $output); + self::assertStringContainsString('Stats - votes registration', $output); + + self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+FALSE/', $output); + self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+FALSE./', $output); + self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+TRUE/', $output); + + self::assertStringContainsString('Sum vote weight | 2', $output); + + self::assertStringContainsString('B '.CondorcetStyle::CONDORCET_WINNER_SYMBOL, $output); # Condorcet Winner + } + + public function testVoteWithDb_CondorcetElectionFormat(): void + { + ElectionCommand::$forceIniMemoryLimitTo = '128M'; + + $this->electionCommand->execute([ + '--votes-per-mb' => 1, + '--import-condorcet-election-format' => __DIR__.'/../../Tools/Converters/CondorcetElectionFormatData/test3.cvotes', + ], [ + 'verbosity' => OutputInterface::VERBOSITY_DEBUG, + ]); + + $output = $this->electionCommand->getDisplay(); + + self::assertMatchesRegularExpression('/Votes per Mb +1/', $output); + self::assertStringContainsString('Db is used', $output); + self::assertStringContainsString('yes, using path:', $output); + + ElectionCommand::$forceIniMemoryLimitTo = null; + + # And absence of this error: unlink(path): Resource temporarily unavailable + } + + public function testFromDebianFormat(): void + { + $this->electionCommand->execute( + [ + '--import-debian-format' => __DIR__.'/../../Tools/Converters/DebianData/leader2020_tally.txt', + 'methods' => ['STV'], + ], + [ + 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, + ] + ); + + $output = $this->electionCommand->getDisplay(); + + self::assertStringContainsString('4 candidates registered || 339 votes registered', $output); + + self::assertStringContainsString('STV', $output); + self::assertStringContainsString('Registered candidates', $output); + self::assertStringContainsString('Stats - votes registration', $output); + + self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+FALSE/', $output); + self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+TRUE./', $output); + self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+TRUE/', $output); + + self::assertStringContainsString('Sum vote weight | 339', $output); + + self::assertStringContainsString('Jonathan Carter '.CondorcetStyle::CONDORCET_WINNER_SYMBOL, $output); # Condorcet Winner + self::assertMatchesRegularExpression('/Seats: *\| 1/', $output); + } + + public function testFromDavidHillFormat(): void + { + $this->electionCommand->execute( + [ + '--import-david-hill-format' => __DIR__.'/../../Tools/Converters/TidemanData/A1.HIL', + 'methods' => ['STV'], + ], + [ + 'verbosity' => OutputInterface::VERBOSITY_VERBOSE, + ] + ); + + $output = $this->electionCommand->getDisplay(); + + self::assertStringContainsString('10 candidates registered || 380 votes registered', $output); + + self::assertStringContainsString('STV', $output); + self::assertStringContainsString('Registered candidates', $output); + self::assertStringContainsString('Stats - votes registration', $output); + + self::assertMatchesRegularExpression('/Is vote weight allowed\?( )+FALSE/', $output); + self::assertMatchesRegularExpression('/Votes are evaluated according to the implicit ranking rule\?( )+TRUE./', $output); + self::assertMatchesRegularExpression('/Is vote tie in rank allowed\?( )+TRUE/', $output); + + self::assertStringContainsString('Sum vote weight | 380', $output); + + self::assertStringContainsString('Candidate 1 '.CondorcetStyle::CONDORCET_WINNER_SYMBOL, $output); # Condorcet Winner + self::assertMatchesRegularExpression('/Seats: *\| 3/', $output); + } +} diff --git a/include/Condorcet/Tests/src/Console/Commands/data.candidates b/include/Condorcet/Tests/src/Console/Commands/data.candidates new file mode 100644 index 0000000..870951a --- /dev/null +++ b/include/Condorcet/Tests/src/Console/Commands/data.candidates @@ -0,0 +1,3 @@ +A +B +C \ No newline at end of file diff --git a/include/Condorcet/Tests/src/Console/Commands/data.votes b/include/Condorcet/Tests/src/Console/Commands/data.votes new file mode 100644 index 0000000..06391b5 --- /dev/null +++ b/include/Condorcet/Tests/src/Console/Commands/data.votes @@ -0,0 +1,2 @@ +A>B=C +B>A>C \ No newline at end of file diff --git a/include/Condorcet/Tests/src/ConstraintTest.php b/include/Condorcet/Tests/src/ConstraintTest.php new file mode 100644 index 0000000..585a9a7 --- /dev/null +++ b/include/Condorcet/Tests/src/ConstraintTest.php @@ -0,0 +1,140 @@ +election = new Election; + + $this->election->addCandidate('A'); + $this->election->addCandidate('B'); + $this->election->addCandidate('C'); + } + + public function testAddConstraintAndClear(): never + { + $this->expectException(VoteConstraintException::class); + $this->expectExceptionMessage('The vote constraint could not be set up: class is already registered'); + + $class = NoTie::class; + + self::assertTrue($this->election->addConstraint($class)); + + self::assertSame([$class], $this->election->getConstraints()); + + self::assertTrue($this->election->clearConstraints()); + + self::assertsame([], $this->election->getConstraints()); + + self::assertTrue($this->election->addConstraint($class)); + + $this->election->addConstraint($class); + } + + public function testPhantomClass(): never + { + $this->expectException(VoteConstraintException::class); + $this->expectExceptionMessage('The vote constraint could not be set up: class is not defined'); + + $this->election->addConstraint('WrongNamespace\AndWrongClass'); + } + + public function testBadClass(): never + { + $this->expectException(VoteConstraintException::class); + $this->expectExceptionMessage('The vote constraint could not be set up: class is not a valid subclass'); + + $class = Vote::class; + + $this->election->addConstraint($class); + } + + public function testConstraintsOnVote(): void + { + $NoTieImplementation = [NoTie::class, AlternativeNoTieConstraintClass::class]; + + foreach ($NoTieImplementation as $constraintClass) { + $this->setUp(); + + $this->election->parseVotes(' + tag1 || A>B>C + C>B=A * 3 + B^42 + '); + + $this->election->allowsVoteWeight(); + + self::assertEquals('B', $this->election->getWinner()); + + $this->election->addConstraint($constraintClass); + + self::assertEquals('A', $this->election->getWinner()); + + $this->election->clearConstraints(); + + self::assertEquals('B', $this->election->getWinner()); + + $this->election->addConstraint($constraintClass); + + self::assertEquals('A', $this->election->getWinner()); + + self::assertEquals(1, $this->election->sumValidVotesWeightWithConstraints()); + self::assertEquals(46, $this->election->sumVotesWeight()); + self::assertEquals(5, $this->election->countVotes()); + self::assertEquals(1, $this->election->countValidVoteWithConstraints()); + self::assertEquals(4, $this->election->countInvalidVoteWithConstraints()); + + self::assertEquals('A', $this->election->getWinner('FTPT')); + + self::assertFalse($this->election->setImplicitRanking(false)); + + self::assertEquals('B', $this->election->getWinner('FTPT')); + self::assertEquals('A', $this->election->getWinner()); + + self::assertEquals(43, $this->election->sumValidVotesWeightWithConstraints()); + self::assertEquals(46, $this->election->sumVotesWeight()); + self::assertEquals(5, $this->election->countVotes()); + self::assertEquals(2, $this->election->countValidVoteWithConstraints()); + self::assertEquals(3, $this->election->countInvalidVoteWithConstraints()); + + self::assertTrue($this->election->setImplicitRanking(true)); + + self::assertEquals('A', $this->election->getWinner()); + self::assertEquals('A', $this->election->getWinner('FTPT')); + + self::assertEquals(1, $this->election->sumValidVotesWeightWithConstraints()); + self::assertEquals(46, $this->election->sumVotesWeight()); + self::assertEquals(5, $this->election->countVotes()); + self::assertEquals(1, $this->election->countValidVoteWithConstraints()); + self::assertEquals(4, $this->election->countInvalidVoteWithConstraints()); + self::assertCount(1, $this->election->getVotesValidUnderConstraintGenerator(['tag1'], true)); + self::assertCount(0, $this->election->getVotesValidUnderConstraintGenerator(['tag1'], false)); + } + } +} + + +class AlternativeNoTieConstraintClass implements VoteConstraintInterface +{ + public static function isVoteAllow(Election $election, Vote $vote): bool + { + foreach ($vote->getContextualRanking($election) as $oneRank) { + if (\count($oneRank) > 1) { + return false; + } + } + + return true; + } +} diff --git a/include/Condorcet/Tests/src/DataManager/ArrayManagerTest.php b/include/Condorcet/Tests/src/DataManager/ArrayManagerTest.php new file mode 100644 index 0000000..6c6ad39 --- /dev/null +++ b/include/Condorcet/Tests/src/DataManager/ArrayManagerTest.php @@ -0,0 +1,50 @@ +ArrayManager = new class (new Election) extends ArrayManager { + protected function preDeletedTask($object): void + { + } + + protected function decodeOneEntity(string $data): Vote + { + $vote = new Vote($data); + $this->getElection()->checkVoteCandidate($vote); + $vote->registerLink($this->Election->get()); + + return $vote; + } + + protected function encodeOneEntity(Vote $data): string + { + $data->destroyLink($this->getElection()); + + return str_replace([' > ', ' = '], ['>', '='], (string) $data); + } + }; + } + + public function testOffsetSetAndOffetsetGet(): void + { + self::assertNull($this->ArrayManager->key()); + + $this->ArrayManager[42] = 'foo'; + + self::assertSame('foo', $this->ArrayManager[42]); + + self::assertNull($this->ArrayManager[43]); + } +} diff --git a/include/Condorcet/Tests/src/DataManager/DataHandlerDrivers/PdoDriver/PdoHandlerDriverTest.php b/include/Condorcet/Tests/src/DataManager/DataHandlerDrivers/PdoDriver/PdoHandlerDriverTest.php new file mode 100644 index 0000000..f93ac19 --- /dev/null +++ b/include/Condorcet/Tests/src/DataManager/DataHandlerDrivers/PdoDriver/PdoHandlerDriverTest.php @@ -0,0 +1,304 @@ + false]); + } + + protected function hashVotesList(Election $elec): string + { + $c = 0; + $voteCompil = ''; + foreach ($elec->getVotesManager() as $oneVote) { + $c++; + $voteCompil .= (string) $oneVote; + } + + return $c.'||'.hash('md5', $voteCompil); + } + + + public function testManyVoteManipulation(): never + { + // Setup + ArrayManager::$CacheSize = 10; + ArrayManager::$MaxContainerLength = 10; + + $electionWithDb = new Election; + $electionInMemory = new Election; + $electionWithDb->setExternalDataHandler($handlerDriver = new PdoHandlerDriver($this->getPDO(), true)); + + // Run Test + + $electionWithDb->parseCandidates('A;B;C;D;E'); + $electionInMemory->parseCandidates('A;B;C;D;E'); + + // 45 Votes + $votes = 'A > C > B > E * 5 + A > D > E > C * 5 + B > E > D > A * 8 + C > A > B > E * 3 + C > A > E > B * 7 + C > B > A > D * 2 + D > C > E > B * 7 + E > B > A > D * 8'; + + $electionWithDb->parseVotes($votes); + $electionInMemory->parseVotes($votes); + + self::assertSame( + $electionWithDb->countVotes(), + $handlerDriver->countEntities() + $electionWithDb->getVotesManager()->getContainerSize() + ); + + self::assertSame($electionInMemory->countVotes(), $electionWithDb->countVotes()); + self::assertSame($electionInMemory->getVotesListAsString(), $electionWithDb->getVotesListAsString()); + self::assertSame($this->hashVotesList($electionInMemory), $this->hashVotesList($electionWithDb)); + + self::assertEquals($electionInMemory->getPairwise()->getExplicitPairwise(), $electionWithDb->getPairwise()->getExplicitPairwise()); + self::assertEquals((string) $electionInMemory->getWinner('Ranked Pairs Winning'), (string) $electionWithDb->getWinner('Ranked Pairs Winning')); + self::assertEquals((string) $electionInMemory->getWinner(), (string) $electionWithDb->getWinner()); + self::assertEquals((string) $electionInMemory->getCondorcetWinner(), (string) $electionWithDb->getCondorcetWinner()); + + + // 58 Votes + $votes = 'A > B > C > E * 58'; + + $electionWithDb->parseVotes($votes); + $electionInMemory->parseVotes($votes); + + self::assertSame(58 % ArrayManager::$MaxContainerLength, $electionWithDb->getVotesManager()->getContainerSize()); + self::assertSame( + $electionWithDb->countVotes(), + $handlerDriver->countEntities() + $electionWithDb->getVotesManager()->getContainerSize() + ); + + self::assertEquals('A', $electionWithDb->getWinner()); + self::assertEquals((string) $electionInMemory->getWinner(), (string) $electionWithDb->getWinner()); + + self::assertSame($electionInMemory->countVotes(), $electionWithDb->countVotes()); + self::assertSame($electionInMemory->getVotesListAsString(), $electionWithDb->getVotesListAsString()); + self::assertSame($this->hashVotesList($electionInMemory), $this->hashVotesList($electionWithDb)); + self::assertSame(0, $electionWithDb->getVotesManager()->getContainerSize()); + self::assertLessThanOrEqual(ArrayManager::$CacheSize, $electionWithDb->getVotesManager()->getCacheSize()); + + // Delete 3 votes + unset($electionInMemory->getVotesManager()[13]); + unset($electionInMemory->getVotesManager()[100]); + unset($electionInMemory->getVotesManager()[102]); + unset($electionWithDb->getVotesManager()[13]); + unset($electionWithDb->getVotesManager()[100]); + unset($electionWithDb->getVotesManager()[102]); + + self::assertSame( + $electionWithDb->countVotes(), + $handlerDriver->countEntities() + $electionWithDb->getVotesManager()->getContainerSize() + ); + self::assertSame($electionInMemory->countVotes(), $electionWithDb->countVotes()); + self::assertSame($electionInMemory->getVotesListAsString(), $electionWithDb->getVotesListAsString()); + self::assertSame($this->hashVotesList($electionInMemory), $this->hashVotesList($electionWithDb)); + self::assertSame(0, $electionWithDb->getVotesManager()->getContainerSize()); + self::assertLessThanOrEqual(ArrayManager::$CacheSize, $electionWithDb->getVotesManager()->getCacheSize()); + self::assertArrayNotHasKey(13, $electionWithDb->getVotesManager()->debugGetCache()); + self::assertArrayNotHasKey(102, $electionWithDb->getVotesManager()->debugGetCache()); + self::assertArrayNotHasKey(100, $electionWithDb->getVotesManager()->debugGetCache()); + self::assertArrayHasKey(101, $electionWithDb->getVotesManager()->debugGetCache()); + + + // Unset Handler + $electionWithDb->removeExternalDataHandler(); + + self::assertEmpty($electionWithDb->getVotesManager()->debugGetCache()); + self::assertSame($electionInMemory->getVotesManager()->getContainerSize(), $electionWithDb->getVotesManager()->getContainerSize()); + self::assertSame($electionInMemory->countVotes(), $electionWithDb->countVotes()); + self::assertSame($electionInMemory->getVotesListAsString(), $electionWithDb->getVotesListAsString()); + self::assertSame($this->hashVotesList($electionInMemory), $this->hashVotesList($electionWithDb)); + + // Change my mind : Set again the a new handler + unset($handlerDriver); + $electionWithDb->setExternalDataHandler($handlerDriver = new PdoHandlerDriver($this->getPDO(), true)); + + self::assertEmpty($electionWithDb->getVotesManager()->debugGetCache()); + self::assertSame(0, $electionWithDb->getVotesManager()->getContainerSize()); + self::assertSame($electionInMemory->countVotes(), $electionWithDb->countVotes()); + self::assertSame($electionInMemory->getVotesListAsString(), $electionWithDb->getVotesListAsString()); + self::assertSame($this->hashVotesList($electionInMemory), $this->hashVotesList($electionWithDb)); + + self::assertTrue($electionWithDb->removeExternalDataHandler()); + + $this->expectException(DataHandlerException::class); + $this->expectExceptionMessage('Problem with data handler: external data handler cannot be removed, is already in use'); + + $electionWithDb->removeExternalDataHandler(); + } + + public function testVotePreserveTag(): void + { + // Setup + ArrayManager::$CacheSize = 10; + ArrayManager::$MaxContainerLength = 10; + + $electionWithDb = new Election; + $electionWithDb->setExternalDataHandler(new PdoHandlerDriver($this->getPDO(), true)); + + $electionWithDb->parseCandidates('A;B;C'); + + $electionWithDb->parseVotes('A > B > C * 5 + tag1 || B > A > C * 3'); + + self::assertSame(5, $electionWithDb->countVotes('tag1', false)); + self::assertSame(3, $electionWithDb->countVotes('tag1', true)); + + $electionWithDb->parseVotes('A > B > C * 5 + tag1 || B > A > C * 3'); + + self::assertSame(10, $electionWithDb->countVotes('tag1', false)); + self::assertSame(6, $electionWithDb->countVotes('tag1', true)); + } + + public function testVoteObjectIntoDataHandler(): void + { + // Setup + ArrayManager::$CacheSize = 10; + ArrayManager::$MaxContainerLength = 10; + + $electionWithDb = new Election; + $electionWithDb->setExternalDataHandler(new PdoHandlerDriver($this->getPDO(), true)); + + $electionWithDb->parseCandidates('A;B;C'); + + $myVote = $electionWithDb->addVote('A>B>C'); + + $electionWithDb->getVotesManager()->regularize(); + self::assertSame(0, $electionWithDb->getVotesManager()->getContainerSize()); + + // myVote is no longer a part of the election. Internally, it will work with clones. + self::assertSame(0, $myVote->countLinks()); + self::assertNotSame($electionWithDb->getVotesList()[0], $myVote); + self::assertTrue($electionWithDb->getVotesList()[0]->haveLink($electionWithDb)); + } + + public function testUpdateEntity(): void + { + // Setup + ArrayManager::$CacheSize = 10; + ArrayManager::$MaxContainerLength = 10; + + $electionWithDb = new Election; + $electionWithDb->setExternalDataHandler(new PdoHandlerDriver($this->getPDO(), true)); + + $electionWithDb->parseCandidates('A;B;C'); + + $electionWithDb->parseVotes('A>B>C * 19'); + $electionWithDb->addVote('C>B>A', 'voteToUpdate'); + + $vote = $electionWithDb->getVotesList('voteToUpdate', true)[19]; + $vote->setRanking('B>A>C'); + $vote = null; + + $electionWithDb->parseVotes('A>B>C * 20'); + + self::assertSame( + "A > B > C * 39\n". + 'B > A > C * 1', + $electionWithDb->getVotesListAsString() + ); + } + + public function testGetVotesListGenerator(): void + { + $electionWithDb = new Election; + $electionWithDb->setExternalDataHandler(new PdoHandlerDriver($this->getPDO(), true)); + + $electionWithDb->parseCandidates('A;B;C'); + + $electionWithDb->parseVotes('A>B>C * 10;tag42 || C>B>A * 42'); + + $votesListGenerator = []; + + foreach ($electionWithDb->getVotesListGenerator() as $key => $value) { + $votesListGenerator[$key] = $value; + } + + self::assertCount(52, $votesListGenerator); + + + $votesListGenerator = []; + + foreach ($electionWithDb->getVotesListGenerator('tag42') as $key => $value) { + $votesListGenerator[$key] = $value; + } + + self::assertCount(42, $votesListGenerator); + } + + public function testSliceInput(): void + { + // Setup + ArrayManager::$CacheSize = 462; + ArrayManager::$MaxContainerLength = 462; + + $electionWithDb = new Election; + $electionWithDb->setExternalDataHandler(new PdoHandlerDriver($this->getPDO(), true)); + + $electionWithDb->parseCandidates('A;B;C'); + + $electionWithDb->parseVotes('A>B>C * 463'); + + self::assertSame(463, $electionWithDb->countVotes()); + } + + public function testMultipleHandler(): never + { + $this->expectException(DataHandlerException::class); + $this->expectExceptionMessage('external data handler cannot be imported'); + + $electionWithDb = new Election; + $electionWithDb->setExternalDataHandler(new PdoHandlerDriver($this->getPDO(), true)); + $electionWithDb->setExternalDataHandler(new PdoHandlerDriver($this->getPDO(), true)); + } + + public function testBadTableSchema1(): never + { + $this->expectException(DataHandlerException::class); + $this->expectExceptionMessage('Problem with data handler: invalid structure template for PdoHandler'); + + $pdo = $this->getPDO(); + $handlerDriver = new PdoHandlerDriver($pdo, true, ['tableName' => 'Entity', 'primaryColumnName' => 42]); + } + + public function testBadTableSchema2(): never + { + $this->expectException(\Exception::class); + + $pdo = $this->getPDO(); + $handlerDriver = new PdoHandlerDriver($pdo, true, ['tableName' => 'B@adName', 'primaryColumnName' => 'id', 'dataColumnName' => 'data']); + } + + public function testEmptyEntities(): void + { + $pdo = $this->getPDO(); + $handlerDriver = new PdoHandlerDriver($pdo, true, ['tableName' => 'Entity', 'primaryColumnName' => 'id', 'dataColumnName' => 'data']); + + self::assertFalse($handlerDriver->selectOneEntity(500)); + + self::assertSame([], $handlerDriver->selectRangeEntities(500, 5)); + } +} diff --git a/include/Condorcet/Tests/src/DataManager/VotesManagerTest.php b/include/Condorcet/Tests/src/DataManager/VotesManagerTest.php new file mode 100644 index 0000000..f012036 --- /dev/null +++ b/include/Condorcet/Tests/src/DataManager/VotesManagerTest.php @@ -0,0 +1,104 @@ +election = new Election; + $this->election->parseCandidates('A;B;C'); + + $this->votes_manager = $this->election->getVotesManager(); + } + + public function testOffsetSet(): never + { + $this->expectException(VoteNotLinkedException::class); + $this->expectExceptionMessage('The vote is not linked to an election'); + + $vote = new Vote([]); + + // add valid vote + $this->votes_manager[] = $vote; + self::assertSame($vote, $this->votes_manager->getVotesList()[0]); + + // add invalid vote + $this->votes_manager[] = null; + } + + public function testOffsetSetArgumentType(): never + { + $this->expectException(VoteManagerException::class); + + // add invalid vote + $this->votes_manager[] = new \stdClass; + } + + public function testOffsetUnset(): void + { + $before_list = $this->votes_manager->getVotesList(); + + // unset non existent vote + unset($this->votes_manager[0]); + self::assertSame($before_list, $this->votes_manager->getVotesList()); + + // unset existing vote + $vote = new Vote([]); + $vote->registerLink($this->election); + $this->votes_manager[] = $vote; + unset($this->votes_manager[0]); + self::assertEmpty($this->votes_manager->getVotesList()); + } + + public function testGetVoteKey(): void + { + self::assertNull($this->votes_manager->getVoteKey(new Vote([]))); + } + + public function testGetVotesList(): void + { + // With Election + self::assertEmpty($this->votes_manager->getVotesList()); + + $this->election->addCandidate('candidate'); + $this->election->addVote(new Vote(['candidate'])); + + self::assertNotEmpty($this->votes_manager->getVotesList()); + } + + public function testGetVotesListGenerator(): void + { + $this->election->parseVotes('A>B>C * 10;tag42 || C>B>A * 42'); + + $votesListGenerator = []; + + foreach ($this->election->getVotesListGenerator() as $key => $value) { + $votesListGenerator[$key] = $value; + } + + self::assertEquals($this->election->getVotesList(), $votesListGenerator); + self::assertCount(52, $votesListGenerator); + + + $votesListGenerator = []; + + foreach ($this->election->getVotesListGenerator('tag42') as $key => $value) { + $votesListGenerator[$key] = $value; + } + + self::assertEquals($this->election->getVotesList('tag42'), $votesListGenerator); + self::assertCount(42, $votesListGenerator); + } +} diff --git a/include/Condorcet/Tests/src/ElectionData/serialized_election_v3.2.0.txt b/include/Condorcet/Tests/src/ElectionData/serialized_election_v3.2.0.txt new file mode 100644 index 0000000..282009f Binary files /dev/null and b/include/Condorcet/Tests/src/ElectionData/serialized_election_v3.2.0.txt differ diff --git a/include/Condorcet/Tests/src/ElectionTest.php b/include/Condorcet/Tests/src/ElectionTest.php new file mode 100644 index 0000000..892e5b1 --- /dev/null +++ b/include/Condorcet/Tests/src/ElectionTest.php @@ -0,0 +1,985 @@ +election1 = new Election; + + $this->candidate1 = $this->election1->addCandidate('candidate1'); + $this->candidate2 = $this->election1->addCandidate('candidate2'); + $this->candidate3 = $this->election1->addCandidate('candidate3'); + + $this->election1->addVote($this->vote1 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3])); + $this->election1->addVote($this->vote2 = new Vote([$this->candidate2, $this->candidate3, $this->candidate1])); + $this->election1->addVote($this->vote3 = new Vote([$this->candidate3, $this->candidate1, $this->candidate2])); + $this->election1->addVote($this->vote4 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3])); + + $this->election2 = new Election; + } + + public function testRemoveVotes(): never + { + $this->expectException(VoteException::class); + $this->expectExceptionMessage('Problem handling vote: cannot remove vote, is not registered in this election'); + + self::assertTrue($this->election1->removeVote($this->vote2)); + self::assertCount(3, $this->election1->getVotesList()); + + $badRemoveVote = new Vote('A'); + + $this->election1->removeVote($badRemoveVote); + } + + public function testRemoveVotesByTags(): void + { + $this->vote1->addtags('tag1,tag2,tag3'); + $this->vote2->addtags('tag3,tag4'); + $this->vote3->addtags('tag3,tag4,tag5'); + $this->vote4->addtags('tag1,tag4'); + + self::assertCount(3, $r = $this->election1->removeVotesByTags(['tag1', 'tag5'])); + + self::assertSame([$this->vote1, $this->vote3, $this->vote4], $r); + + self::assertSame([1 => $this->vote2], $this->election1->getVotesList()); + + $this->setUp(); + + $this->vote1->addtags('tag1,tag2,tag3'); + $this->vote2->addtags('tag3,tag4'); + $this->vote3->addtags('tag3,tag4,tag5'); + $this->vote4->addtags('tag1,tag4'); + + self::assertCount(1, $r = $this->election1->removeVotesByTags('tag1,tag5', false)); + + self::assertSame([$this->vote2], $r); + + self::assertSame([0 => $this->vote1, 2=> $this->vote3, 3 => $this->vote4], $this->election1->getVotesList()); + } + + + public function testTagsFilter(): void + { + $this->vote1->addtags('tag1,tag2,tag3'); + $this->vote2->addtags('tag3,tag4'); + $this->vote3->addtags('tag3,tag4,tag5'); + $this->vote4->addtags('tag1,tag4'); + + self::assertSame($this->election1->getVotesList('tag1,tag2', true), [0=>$this->vote1, 3=>$this->vote4]); + self::assertSame($this->election1->countVotes('tag1,tag2', true), 2); + + self::assertSame($this->election1->getVotesList('tag1,tag2', false), [1=>$this->vote2, 2=>$this->vote3]); + self::assertSame($this->election1->countVotes('tag1,tag2', false), 2); + + $resultGlobal = $this->election1->getResult('Schulze'); + $resultFilter1 = $this->election1->getResult('Schulze', ['tags' => 'tag1', 'withTag' => true]); + $resultFilter2 = $this->election1->getResult('Schulze', ['tags' => 'tag1', 'withTag' => false]); + + self::assertNotSame($resultGlobal, $resultFilter1); + self::assertNotSame($resultGlobal, $resultFilter2); + self::assertNotSame($resultFilter1, $resultFilter2); + } + + public function testParseCandidates(): void + { + self::assertCount( + 4, + $this->election2->parseCandidates('Bruckner; Mahler ; + Debussy + Bibendum') + ); + + self::assertSame( + ['Bruckner', 'Mahler', 'Debussy', 'Bibendum'], + $this->election2->getCandidatesListAsString() + ); + } + + public function testgetCandidateObjectFromName(): void + { + self::assertSame($this->candidate1, $this->election1->getCandidateObjectFromName('candidate1')); + self::assertNull($this->election1->getCandidateObjectFromName('candidate42')); + } + + public function testParseError(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage("The format of the vote is invalid: the value 'text' is not an integer."); + + $this->election1->parseVotes('candidate1>candidate2 * text'); + } + + /** + * @preserveGlobalState disabled + * @backupStaticAttributes disabled + * @runInSeparateProcess + */ + public function testMaxParseIteration1(): never + { + $this->expectException(VoteMaxNumberReachedException::class); + $this->expectExceptionMessage('The maximal number of votes for the method is reached: 42'); + + self::assertSame(42, Election::setMaxParseIteration(42)); + + self::assertCount(42, $this->election1->parseVotes('candidate1>candidate2 * 42')); + + self::assertCount(42, $this->election1->parseVotes('candidate1>candidate2 * 42')); + + self::assertNull(Election::setMaxParseIteration(null)); + + self::assertCount(43, $this->election1->parseVotes('candidate1>candidate2 * 43')); + + self::assertSame(42, Election::setMaxParseIteration(42)); + + $this->election1->parseVotes('candidate1>candidate2 * 43'); + } + + /** + * @preserveGlobalState disabled + * @backupStaticAttributes disabled + * @runInSeparateProcess + */ + public function testMaxParseIteration2(): never + { + $this->expectException(VoteMaxNumberReachedException::class); + $this->expectExceptionMessage('The maximal number of votes for the method is reached: 42'); + + self::assertSame(42, Election::setMaxParseIteration(42)); + + self::assertSame(42, $this->election1->parseVotes(' + candidate1>candidate2 * 21 + candidate1>candidate2 * 21 + candidate1>candidate2 * 21 + ')); + } + + /** + * @preserveGlobalState disabled + * @backupStaticAttributes disabled + * @runInSeparateProcess + */ + public function testMaxParseIteration3(): never + { + $this->expectException(VoteMaxNumberReachedException::class); + $this->expectExceptionMessage('The maximal number of votes for the method is reached: 2'); + + self::assertSame(2, Election::setMaxParseIteration(2)); + + self::assertSame([0=>'candidate1', 1=>'candidate2'], $this->election2->parseCandidates('candidate1;candidate2')); + + self::assertSame([0=>'candidate3', 1=>'candidate4'], $this->election2->parseCandidates('candidate3;candidate4')); + + self::assertNull(Election::setMaxParseIteration(null)); + + self::assertSame([0=>'candidate5', 1=>'candidate6', 2=>'candidate7'], $this->election2->parseCandidates('candidate5;candidate6;candidate7')); + + self::assertSame(2, Election::setMaxParseIteration(2)); + + $this->election2->parseCandidates('candidate8;candidate9;candidate10'); + } + + /** + * @preserveGlobalState disabled + * @backupStaticAttributes disabled + * @runInSeparateProcess + */ + public function testMaxVoteNumber(): never + { + $this->expectException(VoteMaxNumberReachedException::class); + $this->expectExceptionMessage('The maximal number of votes for the method is reached'); + + $election = new Election; + self::assertCount(3, $election->parseCandidates('candidate1;candidate2;candidate3')); + + self::assertSame(42, Election::setMaxVoteNumber(42)); + + self::assertSame(21, $election->parseVotes('candidate1>candidate2 * 21')); + + try { + $election->parseVotes('candidate1>candidate2 * 42'); + self::assertTrue(false); + } catch (VoteMaxNumberReachedException $e) { + $this->assertEquals('The maximal number of votes for the method is reached', $e->getMessage()); + } + + self::assertSame(21, $election->countVotes()); + + $election->parseVotes('candidate1 * 21'); + + self::assertSame(42, $election->countVotes()); + + self::assertNull(Election::setMaxVoteNumber(null)); + + $election->addVote('candidate3'); + + self::assertSame(42, Election::setMaxVoteNumber(42)); + + try { + $election->addVote('candidate3'); + } catch (VoteMaxNumberReachedException $e) { + $reserveException = $e; + } + + self::assertNull(Election::setMaxVoteNumber(null)); + + throw $reserveException; + } + + public function testGetVotesListAsString(): void + { + $this->election1 = new Election; + + $this->election1->addCandidate('C'); + $this->election1->addCandidate('B'); + $this->election1->addCandidate('D'); + $this->election1->addCandidate('E'); + $this->election1->addCandidate('A'); + + $this->election1->parseVotes(' + D * 6 + A * 6 + E = A = B *3 + A > C = B > E * 5 + Y > Z + '); + + self::assertSame( + "A > B = C = D = E * 6\n". + "D > A = B = C = E * 6\n". + "A > B = C > E > D * 5\n". + "A = B = E > C = D * 3\n". + 'A = B = C = D = E * 1', + $this->election1->getVotesListAsString() + ); + + $this->election1->setImplicitRanking(false); + + self::assertSame( + "A * 6\n". + "D * 6\n". + "A > B = C > E * 5\n". + "A = B = E * 3\n". + '/EMPTY_RANKING/ * 1', + $this->election1->getVotesListAsString() + ); + + self::assertSame( + <<<'VOTES' + A * 6 + D * 6 + A > B = C > E * 5 + A = B = E * 3 + Y > Z * 1 + VOTES, + $this->election1->getVotesListAsString(false) + ); + } + + public function testEmptyRankingExport(): void + { + $this->election2->parseCandidates('A;B;C'); + $this->election2->setImplicitRanking(false); + + $this->election2->addVote(new Vote('')); + $this->election2->addVote(new Vote('D>E')); + + self::assertSame('/EMPTY_RANKING/ * 2', $this->election2->getVotesListAsString(true)); + self::assertSame('/EMPTY_RANKING/ * 1'. "\n" .'D > E * 1', $this->election2->getVotesListAsString(false)); + + self::assertSame( + $cvotes_explicit_without_context = + <<<'CVOTES' + #/Candidates: A ; B ; C + #/Implicit Ranking: false + #/Weight Allowed: false + + /EMPTY_RANKING/ * 1 + D > E * 1 + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: false) + ); + + self::assertSame( + str_replace(' * 1', '', $cvotes_explicit_without_context), + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: false, inContext: false) + ); + + self::assertSame( + <<<'CVOTES' + #/Candidates: A ; B ; C + #/Implicit Ranking: false + #/Weight Allowed: false + + /EMPTY_RANKING/ * 2 + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: true) + ); + + self::assertSame( + <<<'CVOTES' + #/Candidates: A ; B ; C + #/Implicit Ranking: false + #/Weight Allowed: false + + /EMPTY_RANKING/ + /EMPTY_RANKING/ + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: false, inContext: true) + ); + + $this->election2->setImplicitRanking(true); + + self::assertSame( + <<<'CVOTES' + #/Candidates: A ; B ; C + #/Implicit Ranking: true + #/Weight Allowed: false + + A = B = C * 2 + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: true) + ); + + self::assertSame( + <<<'CVOTES' + #/Candidates: A ; B ; C + #/Implicit Ranking: true + #/Weight Allowed: false + + A = B = C + A = B = C + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: false, inContext: true) + ); + + $this->election2 = new Election; + $this->election2->parseCandidates('A;B;C;D'); + $this->election2->setImplicitRanking(true); + + $this->election2->addVote(new Vote('A>B')); + + self::assertSame( + <<<'CVOTES' + #/Candidates: A ; B ; C ; D + #/Implicit Ranking: true + #/Weight Allowed: false + + A > B > C = D * 1 + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: true) + ); + + self::assertSame( + $cvotes_implicit_without_context = + <<<'CVOTES' + #/Candidates: A ; B ; C ; D + #/Implicit Ranking: true + #/Weight Allowed: false + + A > B * 1 + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: true, inContext: false) + ); + + self::assertSame( + str_replace(' * 1', '', $cvotes_implicit_without_context), + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $this->election2, includeNumberOfSeats: false, aggregateVotes: false, inContext: false) + ); + } + + public function testParseVoteCandidateCoherence(): void + { + $this->election1 = new Election; + + $cA = $this->election1->addCandidate('A'); + $cB = $this->election1->addCandidate('B'); + $cC = $this->election1->addCandidate('C'); + + self::assertSame(2, $this->election1->parseVotes(' + A>B>C * 2 + ')); + + $votes = $this->election1->getVotesList(); + + foreach ($votes as $vote) { + $ranking = $vote->getRanking(); + + self::assertSame($cA, $ranking[1][0]); + self::assertSame($cB, $ranking[2][0]); + self::assertSame($cC, $ranking[3][0]); + } + } + + public function testParseVotesInvalidPath(): void + { + $this->expectException(FileDoesNotExistException::class); + $this->expectExceptionMessageMatches('/bad_file.txt$/'); + + $this->election1 = new Election; + + $this->election1->addCandidate('A'); + $this->election1->addCandidate('B'); + + $this->election1->parseVotes('bad_file.txt', true); + } + + public function testParseVotesWithoutFail(): void + { + $this->election1 = new Election; + + $this->election1->addCandidate('A'); + $this->election1->addCandidate('B'); + $this->election1->addCandidate('C'); + + self::assertSame(2, $this->election1->parseVotesWithoutFail(' + A > B > C + A > B > C * 4;tag1 || A > B > C*4 #Coucou + A < B < C * 10 + D <> B + A > B > C + ')); + + self::assertSame(10, $this->election1->countVotes()); + + self::assertSame(2, $this->election1->parseVotesWithoutFail(__DIR__.'/../LargeElectionData/smallVote1.votes', true)); + + self::assertSame(20, $this->election1->countVotes()); + + self::assertSame(2, $this->election1->parseVotesWithoutFail(new \SplFileObject(__DIR__.'/../LargeElectionData/smallVote1.votes'), true)); + + self::assertSame(30, $this->election1->countVotes()); + + self::assertSame(2, $this->election1->parseVotesWithoutFail(new \SplFileInfo(__DIR__.'/../LargeElectionData/smallVote1.votes'), true)); + + self::assertSame(40, $this->election1->countVotes()); + } + + public function testParseVotesWithoutFailInvalidPath(): void + { + $this->expectException(FileDoesNotExistException::class); + $this->expectExceptionMessageMatches('/bad_file.txt$/'); + + $this->election1 = new Election; + + $this->election1->addCandidate('A'); + $this->election1->addCandidate('B'); + + $this->election1->parseVotesWithoutFail('bad_file.txt', true); + } + + public function testVoteWeight(): void + { + $election = new Election; + + $election->addCandidate('A'); + $election->addCandidate('B'); + $election->addCandidate('C'); + $election->addCandidate('D'); + + $election->parseVotes(' + A > C > D * 6 + B > A > D * 1 + C > B > D * 3 + D > B > A * 3 + '); + + $voteWithWeight = $election->addVote('D > C > B'); + $voteWithWeight->setWeight(2); + + self::assertSame( + 14, + $election->sumVotesWeight() + ); + + self::assertSame( + 'D > C > B ^2', + (string) $voteWithWeight + ); + + self::assertSame( + 'D > C > B > A', + $voteWithWeight->getSimpleRanking($election) + ); + + self::assertNotSame( + 'A = D > C > B', + $election->getResult('Schulze Winning')->getResultAsString() + ); + + $election->allowsVoteWeight(true); + + self::assertSame( + 15, + $election->sumVotesWeight() + ); + + self::assertSame( + 'D > C > B > A ^2', + $voteWithWeight->getSimpleRanking($election) + ); + + self::assertSame( + 'A = D > C > B', + $election->getResult('Schulze Winning')->getResultAsString() + ); + + $election->allowsVoteWeight(false); + + self::assertSame( + 14, + $election->sumVotesWeight() + ); + + self::assertNotSame( + 'A = D > C > B', + $election->getResult('Schulze Winning')->getResultAsString() + ); + + $election->allowsVoteWeight(!$election->isVoteWeightAllowed()); + + $election->removeVote($voteWithWeight); + + self::assertSame( + 13, + $election->sumVotesWeight() + ); + + $election->parseVotes(' + D > C > B ^2 * 1 + '); + + self::assertSame( + 15, + $election->sumVotesWeight() + ); + + self::assertSame( + 'A = D > C > B', + $election->getResult('Schulze Winning')->getResultAsString() + ); + + $election->addVote('D > C > B'); + + self::assertSame( + <<<'VOTES' + A > C > D > B * 6 + C > B > D > A * 3 + D > B > A > C * 3 + D > C > B > A ^2 * 1 + B > A > D > C * 1 + D > C > B > A * 1 + VOTES, + $election->getVotesListAsString() + ); + } + + public function testaddVotesFromJson(): never + { + $this->expectException(\JsonException::class); + + $election = new Election; + + $election->addCandidate('A'); + $election->addCandidate('B'); + $election->addCandidate('C'); + + $votes = []; + + $votes[]['vote'] = 'B>C>A'; + $votes[]['vote'] = new \stdClass; // Invalid Vote + $votes[]['vote'] = ['C', 'B', 'A']; + + self::assertSame(2, $election->addVotesFromJson(json_encode($votes))); + + self::assertSame( + <<<'VOTES' + B > C > A * 1 + C > B > A * 1 + VOTES, + $election->getVotesListAsString() + ); + + $votes = []; + + $votes[0]['vote'] = 'A>B>C'; + $votes[0]['multi'] = 5; + $votes[0]['tag'] = 'tag1'; + $votes[0]['weight'] = '42'; + + $election->addVotesFromJson(json_encode($votes)); + + $election->allowsVoteWeight(true); + + self::assertSame( + <<<'VOTES' + A > B > C ^42 * 5 + B > C > A * 1 + C > B > A * 1 + VOTES, + $election->getVotesListAsString() + ); + self::assertSame(5, $election->countVotes('tag1')); + + $election->addVotesFromJson(json_encode($votes).'{42'); + } + + public function testaddCandidatesFromJson(): never + { + $this->expectException(CandidateExistsException::class); + $this->expectExceptionMessage('This candidate already exists: candidate2'); + + $election = new Election; + + $candidates = ['candidate1 ', 'candidate2']; + + $election->addCandidatesFromJson(json_encode($candidates)); + + self::assertSame(2, $election->countCandidates()); + + self::assertEquals(['candidate1', 'candidate2'], $election->getCandidatesListAsString()); + + $election->addCandidatesFromJson(json_encode(['candidate2'])); + } + + public function testaddCandidatesFromInvalidJson(): never + { + $this->expectException(\JsonException::class); + + $election = new Election; + + $election->addCandidatesFromJson(json_encode(['candidate3']).'{42'); + } + + + public function testaddVotesFromJsonWithInvalidJson(): void + { + $errors = ['42', 'true', 'false', 'null', '', ' ', json_encode(new \stdClass)]; + + foreach ($errors as $oneError) { + try { + $this->election2->addVotesFromJson($oneError); + + // Else fail + $this->fail("{$oneError} is not a valid Json for PHP"); + } catch (\JsonException) { + } + } + + self::assertEmpty($this->election2->getVotesList()); + } + + public function testCachingResult(): void + { + $election = new Election; + + $election->addCandidate('A'); + $election->addCandidate('B'); + $election->addCandidate('C'); + $election->addCandidate('D'); + + $election->addVote($vote1 = new Vote('A > C > D')); + + $result1 = $election->getResult('Schulze'); + self::assertSame($result1, $election->getResult('Schulze')); + } + + public function testElectionSerializing(): void + { + $election = new Election; + + $election->addCandidate('A'); + $election->addCandidate('B'); + $election->addCandidate('C'); + $election->addCandidate('D'); + + $election->addVote($vote1 = new Vote('A > C > D')); + $result1 = $election->getResult('Schulze'); + + $election = serialize($election); + // file_put_contents("Tests/src/ElectionData/serialized_election_v3.2.0.txt", $election); # For next test + $election = unserialize($election); + + self::assertNotSame($result1, $election->getResult('Schulze')); + self::assertSame($result1->getResultAsString(), $election->getResult('Schulze')->getResultAsString()); + + self::assertNotSame($vote1, $election->getVotesList()[0]); + self::assertSame($vote1->getSimpleRanking(), $election->getVotesList()[0]->getSimpleRanking()); + self::assertTrue($election->getVotesList()[0]->haveLink($election)); + self::assertFalse($vote1->haveLink($election)); + } + + public function testElectionUnserializing(): void + { + $this->expectException(ElectionObjectVersionMismatchException::class); + $this->expectExceptionMessage( + "Version mismatch: The election object has version '3.2' " . + "which is different from the current class version '".Condorcet::getVersion(true)."'" + ); + + unserialize( + file_get_contents('Tests/src/ElectionData/serialized_election_v3.2.0.txt') + ); + } + + public function testCloneElection(): void + { + $this->election1->computeResult(); + + $cloneElection = clone $this->election1; + + self::assertNotSame($this->election1->getVotesManager(), $cloneElection->getVotesManager()); + self::assertSame($this->election1->getVotesList(), $cloneElection->getVotesList()); + + self::assertSame($this->election1->getCandidatesList(), $cloneElection->getCandidatesList()); + + self::assertNotSame($this->election1->getPairwise(), $cloneElection->getPairwise()); + self::assertEquals($this->election1->getExplicitPairwise(), $cloneElection->getExplicitPairwise()); + + self::assertNotSame($this->election1->getTimerManager(), $cloneElection->getTimerManager()); + + self::assertSame($this->election1->getVotesList()[0], $cloneElection->getVotesList()[0]); + + self::assertTrue($cloneElection->getVotesList()[0]->haveLink($this->election1)); + self::assertTrue($cloneElection->getVotesList()[0]->haveLink($cloneElection)); + } + + public function testPairwiseArrayAccess(): void + { + $this->election1->computeResult(); + + self::assertTrue($this->election1->getPairwise()->offsetExists(1)); + } + + public function testGetCandidateObjectFromKey(): void + { + self::assertSame($this->candidate2, $this->election1->getCandidateObjectFromKey(1)); + + self::assertNull($this->election1->getCandidateObjectFromKey(42)); + } + + public function testElectionState1(): never + { + $this->expectException(VotingHasStartedException::class); + $this->expectExceptionMessage("The voting has started: cannot add 'candidate4'"); + + $this->election1->addCandidate('candidate4'); + } + + public function testElectionState2(): never + { + $this->expectException(VotingHasStartedException::class); + $this->expectExceptionMessage('The voting has started'); + + $this->election1->removeCandidates('candidate4'); + } + + public function testElectionState3(): never + { + $this->expectException(NoCandidatesException::class); + $this->expectExceptionMessage('You need to specify one or more candidates before voting'); + + $election = new Election; + $election->setStateTovote(); + } + + public function testElectionState4(): never + { + $this->expectException(ResultRequestedWithoutVotesException::class); + $this->expectExceptionMessage('The result cannot be requested without votes'); + + $election = new Election; + $election->getResult(); + } + + public function testElectionState5(): void + { + $this->election1->getResult(); + + self::assertTrue($this->election1->setStateTovote()); + + self::assertSame(ElectionState::VOTES_REGISTRATION, $this->election1->getState()); + } + + public function testAddSameVote(): never + { + $this->expectException(VoteException::class); + $this->expectExceptionMessage('Problem handling vote: seats are already registered'); + + $this->election1->addVote($this->vote1); + } + + public function testDestroy(): void + { + $election = new Election; + + $election->addCandidate('candidate1'); + $election->addCandidate('candidate2'); + $election->addCandidate('candidate3'); + + $election->addVote('candidate1>candidate2'); + + $weakref = \WeakReference::create($election); + + // PHP circular reference can bug + // \debug_zval_dump($election); + unset($election); + + self::assertNull($weakref->get()); + } + + public function testRemoveCandidate(): never + { + $this->expectException(CandidateDoesNotExistException::class); + $this->expectExceptionMessage('This candidate does not exist: B'); + + $election = new Election; + + $election->addCandidate('candidate1'); + + $badCandidate = new Candidate('B'); + + $election->removeCandidates($badCandidate); + } + + /** + * @dataProvider MethodsListProvider + */ + public function testRemoveCandidateResult(string $method): void + { + $votes = ' Memphis * 4 + Nashville * 3 + Chattanooga * 2 + Knoxville * 1'; + + // Ref + $electionRef = new Election; + + $electionRef->addCandidate('Memphis'); + $electionRef->addCandidate('Nashville'); + $electionRef->addCandidate('Knoxville'); + $electionRef->addCandidate('Chattanooga'); + + $electionRef->parseVotes($votes); + + // Test + $electionTest = new Election; + + $electionTest->addCandidate('Memphis'); + $electionTest->addCandidate('BadCandidate'); + $electionTest->addCandidate('Nashville'); + $electionTest->addCandidate('Knoxville'); + $electionTest->addCandidate('Chattanooga'); + + $electionTest->removeCandidates('BadCandidate'); + + $electionTest->parseVotes($votes); + + + self::assertSame( + $electionRef->getResult($method)->getResultAsArray(true), + $electionTest->getResult($method)->getResultAsArray(true) + ); + } + + public function MethodsListProvider(): array + { + $r = []; + + foreach (Condorcet::getAuthMethods() as $method) { + $r[] = [$method]; + } + + return $r; + } + + public function testAmbiguousCandidatesOnElectionSide(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid'); + + $vote = new Vote('candidate1>candidate2'); + + $election1 = new Election; + $election2 = new Election; + + $election1->addCandidate(new Candidate('candidate2')); + $election2->addCandidate(new Candidate('candidate1')); + + $election1->addVote($vote); + $election2->addVote($vote); + } + + public function testAmbiguousCandidatesOnVoteSide(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: vote does not match candidate in this election'); + + $election1 = new Election; + $election2 = new Election; + + $election1->addCandidate(new Candidate('candidate1')); + $election2->addCandidate(new Candidate('candidate2')); + + $candidate3 = new Candidate('candidate3'); + $election1->addCandidate($candidate3); + $election2->addCandidate($candidate3); + + $vote = new Vote('candidate3'); + + $election1->addVote($vote); + $election2->addVote($vote); + + $election1->getResult(); + $election2->getResult(); + + try { + $vote->setRanking('candidate1>candidate2>candidate3'); + } catch (\Exception $e) { + } + + self::assertEmpty($election1->debugGetCalculator()); + self::assertEmpty($election2->debugGetCalculator()); + + throw $e; + } + + public function testInvalidSeats(): never + { + $this->expectException(NoSeatsException::class); + $this->expectExceptionMessage('No seats defined'); + + $this->election1->setNumberOfSeats(0); + } + + public function testSeats(): void + { + self::assertSame(100, $this->election1->getNumberOfSeats()); + + $this->election1->setNumberOfSeats(5); + + self::assertSame(5, $this->election1->getNumberOfSeats()); + } +} diff --git a/include/Condorcet/Tests/src/ResultTest.php b/include/Condorcet/Tests/src/ResultTest.php new file mode 100644 index 0000000..a9c555b --- /dev/null +++ b/include/Condorcet/Tests/src/ResultTest.php @@ -0,0 +1,334 @@ +election1 = new Election; + } + + public function testGetResultAsString(): void + { + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->parseVotes(' + B > A > C * 7 + A > B > C * 7 + C > A > B * 2 + C > B > A * 2 + '); + + + self::assertSame( + 'A = B > C', + $this->election1->getResult('Ranked Pairs')->getResultAsString() + ); + } + + public function testGetResultAsInternalKey(): void + { + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->parseVotes(' + B > A > C * 7 + A > B > C * 7 + C > A > B * 2 + C > B > A * 2 + '); + + + self::assertSame( + [1 => [0, 1], 2 => [2]], + $this->election1->getResult('Ranked Pairs')->getResultAsInternalKey() + ); + } + + public function testgetCondorcetElectionGeneratorVersion(): void + { + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('C > B > A'); + + self::assertSame(Condorcet::getVersion(), $this->election1->getResult('Ranked Pairs')->getCondorcetElectionGeneratorVersion()); + } + + public function testResultClassgenerator(): void + { + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('C > B > A'); + + self::assertSame(\CondorcetPHP\Condorcet\Algo\Methods\RankedPairs\RankedPairsMargin::class, $this->election1->getResult('Ranked Pairs')->getClassGenerator()); + } + + public function testMethod(): void + { + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('C > B > A'); + + self::assertSame('Ranked Pairs Margin', $this->election1->getResult('Ranked Pairs')->getMethod()); + } + + public function testGetBuildTimeStamp(): void + { + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('C > B > A'); + + self::assertIsFloat($this->election1->getResult('Ranked Pairs')->getBuildTimeStamp()); + } + + public function testGetWinner(): void + { + $this->election1->addCandidate('a'); + $this->election1->addCandidate('b'); + $this->election1->addCandidate('c'); + + $this->election1->parseVotes(' + a > c > b * 23 + b > c > a * 19 + c > b > a * 16 + c > a > b * 2 + '); + + self::assertEquals('c', $this->election1->getResult()->getWinner()); + self::assertEquals('c', $this->election1->getResult()->getCondorcetWinner()); + } + + public function testGetLoser(): void + { + $this->election1->addCandidate('Memphis'); + $this->election1->addCandidate('Nashville'); + $this->election1->addCandidate('Knoxville'); + $this->election1->addCandidate('Chattanooga'); + + $this->election1->parseVotes(' + Memphis > Nashville > Chattanooga * 42 + Nashville > Chattanooga > Knoxville * 26 + Chattanooga > Knoxville > Nashville * 15 + Knoxville > Chattanooga > Nashville * 17 + '); + + self::assertEquals('Memphis', $this->election1->getResult()->getLoser()); + self::assertEquals('Memphis', $this->election1->getResult()->getCondorcetLoser()); + } + + public function testgetOriginalResultArrayWithString(): void + { + $this->election1->addCandidate('a'); + $this->election1->addCandidate('b'); + $this->election1->addCandidate('c'); + + $this->election1->addVote('a > b > c'); + + self::assertEquals( + [1 => 'a', + 2 => 'b', + 3 => 'c', + ], + $this->election1->getResult()->getOriginalResultArrayWithString() + ); + } + + public function testOffsetSet(): never + { + $this->expectException(ResultException::class); + $this->expectExceptionMessage('Result cannot be changed'); + + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('C > B > A'); + + $result = $this->election1->getResult('Schulze'); + + $result[] = 42; + } + + public function testOffUnset(): never + { + $this->expectException(ResultException::class); + $this->expectExceptionMessage('Result cannot be changed'); + + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('C > B > A'); + + $result = $this->election1->getResult('Schulze'); + + self::assertTrue(isset($result[1])); + + unset($result[1]); + } + + public function testIterator(): void + { + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $vote = $this->election1->addVote('C > B > A'); + + $result = $this->election1->getResult('Schulze'); + + foreach ($result as $key => $value) { + self::assertSame($vote->getRanking()[$key], $value); + } + } + + public function testBadMethodName(): never + { + $this->expectException(AlgorithmException::class); + $this->expectExceptionMessage('The voting algorithm is not available: bad method'); + + $this->election1->addCandidate('B'); + $this->election1->addCandidate('A'); + $this->election1->addCandidate('C'); + + $this->election1->parseVotes('A>B>C'); + + $this->election1->getResult('bad method'); + } + + public function testResultRankOrdering(): void + { + $this->election1->addCandidate('B'); + $this->election1->addCandidate('C'); + $this->election1->addCandidate('A'); + + $this->election1->addVote('C = A = B'); + + self::assertSame( + [1 => ['A', 'B', 'C'], + ], + $this->election1->getResult()->getOriginalResultArrayWithString() + ); + } + + public function testProportional(): void + { + $this->election1->addCandidate('A'); + $this->election1->addCandidate('B'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('A'); + + $this->election1->setNumberOfSeats(2); + + $result = $this->election1->getResult('STV'); + + self::assertSame( + 2, + $result->getNumberOfSeats() + ); + + self::assertTrue( + $result->getClassGenerator()::IS_PROPORTIONAL + ); + + self::assertTrue( + $result->isProportional() + ); + + $result = $this->election1->getResult('Schulze'); + + self::assertNull( + $result->getNumberOfSeats() + ); + + self::assertFalse( + $result->getClassGenerator()::IS_PROPORTIONAL + ); + + self::assertFalse( + $result->isProportional() + ); + } + + public function testMethodOption(): void + { + $this->election1->addCandidate('A'); + $this->election1->addCandidate('B'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('A>B>C'); + + $class = Condorcet::getMethodClass('Borda'); + + self::assertSame(1, $class::$optionStarting); + + $b1 = $this->election1->getResult('Borda'); + $c1 = $this->election1->getResult('Copeland'); + + self::assertSame(1, $b1->getMethodOptions()['Starting']); + + self::assertTrue($this->election1->setMethodOption('Borda Count', 'Starting', 0)); + self::assertSame(0, $class::$optionStarting); + + self::assertSame(1, $b1->getMethodOptions()['Starting']); + + $b2 = $this->election1->getResult('Borda'); + $c2 = $this->election1->getResult('Copeland'); + + self::assertNotSame($b1, $b2); + self::assertSame($c1, $c2); + + self::assertSame(0, $b2->getMethodOptions()['Starting']); + + self::assertFalse($this->election1->setMethodOption('Unregistered method', 'Starting', 0)); + } + + public function testVerbosityLevel(): void + { + $this->election1->addCandidate('A'); + $this->election1->addCandidate('B'); + $this->election1->addCandidate('C'); + + $this->election1->addVote('A>B>C'); + + $r1 = $this->election1->getResult(KemenyYoung::class); + self::assertSame(StatsVerbosity::STD, $r1->statsVerbosity); + + $this->election1->setStatsVerbosity(StatsVerbosity::STD); + $r2 = $this->election1->getResult(KemenyYoung::class); + self::assertSame($r1, $r2); + + $this->election1->setStatsVerbosity(StatsVerbosity::FULL); + $r3 = $this->election1->getResult(KemenyYoung::class); + + self::assertSame(StatsVerbosity::STD, $r1->statsVerbosity); + self::assertSame(StatsVerbosity::FULL, $r3->statsVerbosity); + + self::assertNotSame($r1, $r3); + self::assertArrayNotHasKey('Ranking Scores', $r1->getStats()); + self::assertArrayHasKey('Ranking Scores', $r3->getStats()); + } +} diff --git a/include/Condorcet/Tests/src/Throwable/CondorcetInternal/CondorcetInternalErrorTest.php b/include/Condorcet/Tests/src/Throwable/CondorcetInternal/CondorcetInternalErrorTest.php new file mode 100644 index 0000000..e2f1ecb --- /dev/null +++ b/include/Condorcet/Tests/src/Throwable/CondorcetInternal/CondorcetInternalErrorTest.php @@ -0,0 +1,19 @@ +expectException(CondorcetInternalError::class); + $this->expectExceptionMessage($message = 'Test message'); + + throw new CondorcetInternalError($message); + } +} diff --git a/include/Condorcet/Tests/src/Timer/TimerTest.php b/include/Condorcet/Tests/src/Timer/TimerTest.php new file mode 100644 index 0000000..2776f57 --- /dev/null +++ b/include/Condorcet/Tests/src/Timer/TimerTest.php @@ -0,0 +1,27 @@ +expectException(TimerException::class); + $this->expectExceptionMessage('Only a chrono linked to this manager can be used'); + + $manager1 = new Manager; + $manager2 = new Manager; + + $chrono1 = new Chrono($manager1); + $chrono2 = new Chrono($manager2); + + $manager1->addTime($chrono1); + $manager1->addTime($chrono2); + } +} diff --git a/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test1.cvotes b/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test1.cvotes new file mode 100644 index 0000000..3927c0f --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test1.cvotes @@ -0,0 +1,5 @@ +#/Candidates:A;B;C +#/Implicit Ranking: true +#/Weight Allowed: false + +A>B ^2 \ No newline at end of file diff --git a/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test2.cvotes b/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test2.cvotes new file mode 100644 index 0000000..4d4c216 --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test2.cvotes @@ -0,0 +1,7 @@ +#/Candidates:A;B;C +#/Implicit Ranking: false +#/Weight Allowed: false +#Number of Seats: 42 + +B>A ^2 +B>C \ No newline at end of file diff --git a/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test3.cvotes b/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test3.cvotes new file mode 100644 index 0000000..dfe9d9e --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatData/test3.cvotes @@ -0,0 +1,3 @@ +#/Candidates:A;B;C + +C>B>A * 700 \ No newline at end of file diff --git a/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatTest.php b/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatTest.php new file mode 100644 index 0000000..d8c8470 --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/CondorcetElectionFormatTest.php @@ -0,0 +1,402 @@ +fwrite(<<<'CVOTES' + #/Candidates: Richard Boháč ; Petr Němec ; Simona Slaná + Richard Boháč>Petr Němec ^42 + CVOTES); + + $condorcetFormat = new CondorcetElectionFormat($file); + self::assertFalse($condorcetFormat->CandidatesParsedFromVotes); + + + $election = $condorcetFormat->setDataToAnElection(); + + self::assertFalse($election->isVoteWeightAllowed()); + $election->allowsVoteWeight(true); + self::assertTrue($election->isVoteWeightAllowed()); + + + self::assertSame(['Petr Němec', 'Richard Boháč', 'Simona Slaná'], $election->getCandidatesListAsString()); + + self::assertSame(100, $election->getNumberOfSeats()); + self::assertTrue($election->getImplicitRankingRule()); + + self::assertSame(1, $election->countVotes()); + self::assertSame('Richard Boháč > Petr Němec > Simona Slaná ^42', $election->getVotesList()[0]->getSimpleRanking(context: $election, displayWeight: true)); + self::assertSame(0, $condorcetFormat->invalidBlocksCount); + } + + + public function testCondorcetElectionFormat2_MultiplesErrorsAndComplications(): void + { + $file = new \SplTempFileObject; + $file->fwrite(<<<'CVOTES' + #/Candidates: A ;B;C + #/Number of Seats: 6 + #/Implicit Ranking: false + + A > B > C + A > B > C * 4;tag1 || A > B > C*4 #Coucou + # A > B > C + A < B < C * 10 + A > E > A* 3 + D <> B + A > B > C + CVOTES); + + $condorcetFormat = new CondorcetElectionFormat($file); + + $election = $condorcetFormat->setDataToAnElection(); + + self::assertSame(['A', 'B', 'C'], $election->getCandidatesListAsString()); + + self::assertSame(6, $election->getNumberOfSeats()); + + self::assertFalse($election->getImplicitRankingRule()); + + self::assertSame(10, $election->countVotes()); + + self::assertSame(3, $condorcetFormat->invalidBlocksCount); + + self::assertSame(['tag1'], $election->getVotesList()[5]->getTags()); + } + + public function testCondorcetElectionFormat3_CustomElection1(): void + { + $file = new \SplTempFileObject; + $file->fwrite(<<<'CVOTES' + #/Candidates: Richard Boháč ; Petr Němec ; Simona Slaná + #/Number of Seats: 42 + #/Implicit Ranking: true + Richard Boháč>Petr Němec ^42 + CVOTES); + + $condorcetFormat = new CondorcetElectionFormat($file); + + $election = new Election; + $election->setImplicitRanking(false); + $election->setNumberOfSeats(66); + + $condorcetFormat->setDataToAnElection($election); + + self::assertSame(['Petr Němec', 'Richard Boháč', 'Simona Slaná'], $election->getCandidatesListAsString()); + + self::assertSame(42, $election->getNumberOfSeats()); + self::assertTrue($election->getImplicitRankingRule()); + + self::assertSame(1, $election->countVotes()); + self::assertSame('Richard Boháč > Petr Němec > Simona Slaná', $election->getVotesList()[0]->getSimpleRanking(context: $election, displayWeight: true)); + self::assertSame(0, $condorcetFormat->invalidBlocksCount); + } + + public function testCondorcetElectionFormat4_CustomElection2(): void + { + $file = new \SplTempFileObject; + $file->fwrite(<<<'CVOTES' + #/Candidates: Richard Boháč ; Petr Němec ; Simona Slaná + #/Weight allowed: true + Richard Boháč>Petr Němec ^42 + CVOTES); + + $condorcetFormat = new CondorcetElectionFormat($file); + + $election = new Election; + $election->setImplicitRanking(false); + $election->setNumberOfSeats(66); + $election->allowsVoteWeight(false); + + $condorcetFormat->setDataToAnElection($election); + + $election->allowsVoteWeight(true); // Must be forced by parameter + + + self::assertSame(['Petr Němec', 'Richard Boháč', 'Simona Slaná'], $election->getCandidatesListAsString()); + + self::assertSame(66, $election->getNumberOfSeats()); + self::assertFalse($election->getImplicitRankingRule()); + + self::assertSame(1, $election->countVotes()); + self::assertSame('Richard Boháč > Petr Němec ^42', $election->getVotesList()[0]->getSimpleRanking(context: $election, displayWeight: true)); + self::assertSame(0, $condorcetFormat->invalidBlocksCount); + } + + public function testCondorcetElectionFormat5_UnknowParametersAndEmptyLinesAndCase(): void + { + $file = new \SplTempFileObject; + $file->fwrite(<<<'CVOTES' + #/Candidates: Richard Boháč ; 郝文彦 ; Simona Slaná + + #/AnewParameters: 7 + #/numBer of Seats: 42 + #/implicit ranking: true + + + + Richard Boháč>郝文彦 ^42 + CVOTES); + + $condorcetFormat = new CondorcetElectionFormat($file); + + $election = new Election; + $election->setImplicitRanking(false); + $election->setNumberOfSeats(66); + + $condorcetFormat->setDataToAnElection($election); + + self::assertSame(['Richard Boháč', 'Simona Slaná', '郝文彦'], $election->getCandidatesListAsString()); + + self::assertSame(42, $election->getNumberOfSeats()); + self::assertTrue($election->getImplicitRankingRule()); + + self::assertSame(1, $election->countVotes()); + self::assertSame('Richard Boháč > 郝文彦 > Simona Slaná', $election->getVotesList()[0]->getSimpleRanking(context: $election, displayWeight: true)); + self::assertSame(0, $condorcetFormat->invalidBlocksCount); + } + + public function testOfficialSpecificationValidExamples(): void + { + # Example with tags and implicit ranking + $file = new \SplTempFileObject; + $file->fwrite(<<<'CVOTES' + # My beautiful election + #/Candidates: Candidate A;Candidate B;Candidate C + #/Implicit Ranking: true + #/Weight allowed: true + + # Here the votes datas: + Candidate A > Candidate B > Candidate C * 42 + julien@condorcet.vote , signature:55073db57b0a859911 || Candidate A > Candidate B > Candidate C # Same as above, so there will be 43 votes with this ranking. And tags are registered by the software if able. + Candidate C > Candidate A = Candidate B ^7 * 8 # 8 votes with a weight of 7. + Candidate B = Candidate A > Candidate C + Candidate C # Interpreted as Candidate C > Candidate A = Candidate B, because implicit ranking is true (wich is also default, but it's better to say it) + Candidate B > Candidate C # Interpreted as Candidate B > Candidate C + CVOTES); + + $condorcetFormat = new CondorcetElectionFormat($file); + $election = $condorcetFormat->setDataToAnElection(); + + self::assertSame(54, $election->countVotes()); + + self::assertSame(<<<'VOTES' + Candidate C > Candidate A = Candidate B ^7 * 8 + Candidate A > Candidate B > Candidate C * 43 + Candidate A = Candidate B > Candidate C * 1 + Candidate B > Candidate C > Candidate A * 1 + Candidate C > Candidate A = Candidate B * 1 + VOTES + , $election->getVotesListAsString()); + + + self::assertCount(1, $election->getVotesList(tags: 'signature:55073db57b0a859911', with: true)); + self::assertCount(1, $election->getVotesList(tags: 'julien@condorcet.vote', with: true)); + self::assertCount(0, $election->getVotesList(tags: 'otherTag', with: true)); + self::assertSame('Candidate A > Candidate B > Candidate C', current($election->getVotesList(tags: 'julien@condorcet.vote', with: true))->getSimpleRanking()); + + + # Example without implicit ranking as weight + $file = new \SplTempFileObject; + $file->fwrite(<<<'CVOTES' + # My beautiful election + #/Candidates: Candidate A ; Candidate B ; Candidate C + #/Implicit Ranking: false + #/Weight allowed: false + + # Here the votes datas: + Candidate A > Candidate B > Candidate C ^7 *2 # Vote weight is disable, so ^7 is ignored. Two vote with weight of 1. + Candidate C>Candidate B # Vote is untouched. When compute pairwise, Candidate C win again Candidate B, no one beats the candidate or achieves a draw. + Candidate B # Vote is valid, but not have any effect on most election method, especially Condorcet methods. + CVOTES); + + $condorcetFormat = new CondorcetElectionFormat($file); + $election = $condorcetFormat->setDataToAnElection(); + + self::assertSame(4, $election->countVotes()); + + self::assertSame(<<<'VOTES' + Candidate A > Candidate B > Candidate C * 2 + Candidate B * 1 + Candidate C > Candidate B * 1 + VOTES + , $election->getVotesListAsString()); + } + + public function testexportElectionToCondorcetElectionFormat(): void + { + $input = new \SplTempFileObject; + $input->fwrite(<<<'CVOTES' + #/Weight allowed: true + #/Candidates: Richard Boháč ; Petr Němec ; Simona Slaná + #/Number of Seats: 42 + #/Implicit Ranking: true + + Richard Boháč>Petr Němec ^7 + Richard Boháč>Petr Němec + tag1 , tag b || Richard Boháč>Petr Němec + Simona Slaná * 2 + Petr Němec *1 + CVOTES); + + $election = (new CondorcetElectionFormat($input))->setDataToAnElection(); + + self::assertSame( + $assertion1 = + <<<'CVOTES' + #/Candidates: Petr Němec ; Richard Boháč ; Simona Slaná + #/Number of Seats: 42 + #/Implicit Ranking: true + #/Weight Allowed: true + + Richard Boháč > Petr Němec ^7 * 1 + Richard Boháč > Petr Němec * 2 + Simona Slaná * 2 + Petr Němec * 1 + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $election) + ); + + self::assertStringNotContainsString('Number of Seats: 42', CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $election, includeNumberOfSeats: false)); + + $election->setImplicitRanking(false); + self::assertSame( + <<<'CVOTES' + #/Candidates: Petr Němec ; Richard Boháč ; Simona Slaná + #/Number of Seats: 42 + #/Implicit Ranking: false + #/Weight Allowed: true + + Richard Boháč > Petr Němec ^7 * 1 + Richard Boháč > Petr Němec * 2 + Simona Slaná * 2 + Petr Němec * 1 + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $election) + ); + + self::assertSame( + <<<'CVOTES' + #/Candidates: Petr Němec ; Richard Boháč ; Simona Slaná + #/Number of Seats: 42 + #/Implicit Ranking: false + #/Weight Allowed: true + + Richard Boháč > Petr Němec ^7 + Richard Boháč > Petr Němec + tag1,tag b || Richard Boháč > Petr Němec + Simona Slaná + Simona Slaná + Petr Němec + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $election, aggregateVotes: false) + ); + + self::assertSame( + $assertion5 = + <<<'CVOTES' + #/Candidates: Petr Němec ; Richard Boháč ; Simona Slaná + #/Number of Seats: 42 + #/Implicit Ranking: false + #/Weight Allowed: true + + Richard Boháč > Petr Němec ^7 + Richard Boháč > Petr Němec + Richard Boháč > Petr Němec + Simona Slaná + Simona Slaná + Petr Němec + CVOTES, + CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $election, aggregateVotes: false, includeTags: false) + ); + + $election->setImplicitRanking(true); + $output = new \SplTempFileObject; + self::assertNull(CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $election, file: $output)); + $output->rewind(); + + self::assertSame( + $assertion1, + $output->fread(2048) + ); + + $election->setImplicitRanking(false); + $output = new \SplTempFileObject; + self::assertNull(CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $election, aggregateVotes: false, includeTags: false, file: $output)); + $output->rewind(); + self::assertSame( + $assertion5, + $output->fread(2048) + ); + } + + public function testEmptyRankingImport(): void + { + $file = new \SplTempFileObject; + $file->fwrite($input = <<<'CVOTES' + #/Candidates: A ; B ; C + #/Number of Seats: 42 + #/Implicit Ranking: false + #/Weight Allowed: false + + /EMPTY_RANKING/ * 1 + D > E * 1 + CVOTES); + + $cef = new CondorcetElectionFormat($file); + + $election = $cef->setDataToAnElection(); + + self::assertSame('/EMPTY_RANKING/ * 2', $election->getVotesListAsString()); + self::assertSame([], $election->getVotesList()[0]->getRanking()); + self::assertSame($input, CondorcetElectionFormat::exportElectionToCondorcetElectionFormat($election)); + } + + public function testCandidatesFromVotes(): void + { + $file = new \SplTempFileObject; + $file->fwrite($input = <<<'CVOTES' + #/Number of Seats: 42 + #/Implicit Ranking: false + #/Weight Allowed: false + + /EMPTY_RANKING/ * 1 + D > E^7 * 2 # Comment + + tag1, tag2 ||A= B > C = D > F + D > F = A + D>A>B>C>E>F + CVOTES); + + $cef = new CondorcetElectionFormat($file); + + self::assertSame(['A', 'B', 'C', 'D', 'E', 'F'], $cef->candidates); + self::assertTrue($cef->CandidatesParsedFromVotes); + + $election = $cef->setDataToAnElection(); + + self::assertFalse($election->getImplicitRankingRule()); + self::assertSame(42, $election->getNumberOfSeats()); + + self::assertEquals(['A', 'B', 'C', 'D', 'E', 'F'], $election->getCandidatesList()); + self::assertSame('D > A > B > C > E > F', $election->getResult()->getResultAsString()); + } + + public function testFileDoesNotExists(): void + { + $this->expectException(FileDoesNotExistException::class); + + new CondorcetElectionFormat(__DIR__.'noFile.txt'); + } +} diff --git a/include/Condorcet/Tests/src/Tools/Converters/DavidHillFormatTest.php b/include/Condorcet/Tests/src/Tools/Converters/DavidHillFormatTest.php new file mode 100644 index 0000000..2570dd2 --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/DavidHillFormatTest.php @@ -0,0 +1,380 @@ +setDataToAnElection(); + + self::assertSame(213, $election->countVotes()); + self::assertSame(1, $election->getNumberOfSeats()); + + self::assertSame( + <<<'EOD' + 3 > 1 = 2 * 39 + 1 > 3 > 2 * 38 + 3 > 1 > 2 * 36 + 3 > 2 > 1 * 29 + 1 > 2 > 3 * 28 + 2 > 1 > 3 * 15 + 1 > 2 = 3 * 14 + 2 > 3 > 1 * 9 + 2 > 1 = 3 * 5 + EOD, + $election->getVotesListAsString() + ); + } + + public function testA77_With_Explicit(): void + { + $election = new Election; + $election->setImplicitRanking(false); + + self::$tidemanA77->setDataToAnElection($election); + + self::assertSame(213, $election->countVotes()); + + self::assertSame( + <<<'EOD' + 3 * 39 + 1 > 3 * 38 + 3 > 1 * 36 + 3 > 2 * 29 + 1 > 2 * 28 + 2 > 1 * 15 + 1 * 14 + 2 > 3 * 9 + 2 * 5 + EOD, + $election->getVotesListAsString() + ); + } + + public function testA1_ForCandidatesNames(): void + { + $election = (new DavidHillFormat(__DIR__.'/TidemanData/A1.HIL'))->setDataToAnElection(); + + self::assertSame(380, $election->countVotes()); + self::assertSame(3, $election->getNumberOfSeats()); + + self::assertSame( + <<<'EOD' + Candidate 3 > Candidate 1 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 13 + Candidate 1 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 9 + Candidate 1 > Candidate 3 > Candidate 9 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 9 + Candidate 2 > Candidate 8 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 6 + Candidate 1 > Candidate 5 > Candidate 8 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 5 + Candidate 1 > Candidate 3 > Candidate 9 > Candidate 7 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 4 + Candidate 1 > Candidate 8 > Candidate 4 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 4 + Candidate 1 > Candidate 9 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 4 + Candidate 3 > Candidate 6 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 9 = Candidate 10 * 4 + Candidate 4 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 4 + Candidate 7 > Candidate 9 > Candidate 3 > Candidate 1 = Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 4 + Candidate 9 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 4 + Candidate 9 > Candidate 8 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 4 + Candidate 1 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 3 + Candidate 1 > Candidate 3 > Candidate 2 > Candidate 7 > Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 9 = Candidate 10 * 3 + Candidate 1 > Candidate 3 > Candidate 4 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 3 + Candidate 1 > Candidate 4 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 3 + Candidate 1 > Candidate 4 > Candidate 9 > Candidate 3 > Candidate 8 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 3 + Candidate 1 > Candidate 5 > Candidate 9 > Candidate 2 > Candidate 7 > Candidate 10 > Candidate 3 = Candidate 4 = Candidate 6 = Candidate 8 * 3 + Candidate 1 > Candidate 7 > Candidate 9 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 3 + Candidate 1 > Candidate 8 > Candidate 4 > Candidate 9 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 3 + Candidate 1 > Candidate 9 > Candidate 5 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 3 + Candidate 2 > Candidate 4 > Candidate 8 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 3 + Candidate 2 > Candidate 9 > Candidate 7 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 3 + Candidate 3 > Candidate 1 > Candidate 9 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 3 + Candidate 7 > Candidate 9 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 3 + Candidate 9 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 3 + Candidate 1 > Candidate 3 > Candidate 2 > Candidate 7 > Candidate 9 > Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 2 + Candidate 1 > Candidate 5 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 2 + Candidate 1 > Candidate 7 > Candidate 9 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 2 + Candidate 1 > Candidate 9 > Candidate 8 > Candidate 2 > Candidate 3 > Candidate 7 > Candidate 4 > Candidate 10 > Candidate 5 = Candidate 6 * 2 + Candidate 1 > Candidate 10 > Candidate 9 > Candidate 2 > Candidate 3 > Candidate 4 > Candidate 5 > Candidate 6 > Candidate 7 > Candidate 8 * 2 + Candidate 2 > Candidate 3 > Candidate 9 > Candidate 10 > Candidate 8 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 * 2 + Candidate 2 > Candidate 7 > Candidate 8 > Candidate 9 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 2 + Candidate 2 > Candidate 7 > Candidate 9 > Candidate 8 > Candidate 10 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 * 2 + Candidate 2 > Candidate 8 > Candidate 7 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 9 = Candidate 10 * 2 + Candidate 2 > Candidate 8 > Candidate 7 > Candidate 9 > Candidate 4 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 10 * 2 + Candidate 2 > Candidate 9 > Candidate 8 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 2 + Candidate 4 > Candidate 1 > Candidate 9 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 2 + Candidate 4 > Candidate 3 > Candidate 6 > Candidate 1 = Candidate 2 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 2 + Candidate 4 > Candidate 9 > Candidate 6 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 10 * 2 + Candidate 6 > Candidate 7 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 9 = Candidate 10 * 2 + Candidate 6 > Candidate 7 > Candidate 8 > Candidate 9 > Candidate 1 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 10 * 2 + Candidate 7 > Candidate 1 > Candidate 3 > Candidate 9 > Candidate 8 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 2 + Candidate 8 > Candidate 1 > Candidate 3 > Candidate 4 > Candidate 2 > Candidate 5 > Candidate 10 > Candidate 9 > Candidate 7 > Candidate 6 * 2 + Candidate 8 > Candidate 1 > Candidate 7 > Candidate 9 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 2 + Candidate 8 > Candidate 1 > Candidate 10 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 * 2 + Candidate 8 > Candidate 2 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 2 + Candidate 8 > Candidate 2 > Candidate 1 > Candidate 3 > Candidate 4 > Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 2 + Candidate 8 > Candidate 2 > Candidate 6 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 9 = Candidate 10 * 2 + Candidate 8 > Candidate 2 > Candidate 7 > Candidate 9 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 2 + Candidate 8 > Candidate 3 > Candidate 1 > Candidate 6 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 9 = Candidate 10 * 2 + Candidate 8 > Candidate 3 > Candidate 1 > Candidate 6 > Candidate 9 > Candidate 7 > Candidate 10 > Candidate 2 = Candidate 4 = Candidate 5 * 2 + Candidate 9 > Candidate 1 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 2 + Candidate 9 > Candidate 2 > Candidate 7 > Candidate 8 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 2 + Candidate 9 > Candidate 4 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 2 + Candidate 9 > Candidate 4 > Candidate 3 > Candidate 1 = Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 2 + Candidate 9 > Candidate 4 > Candidate 3 > Candidate 1 > Candidate 7 > Candidate 8 > Candidate 6 > Candidate 2 > Candidate 10 > Candidate 5 * 2 + Candidate 9 > Candidate 7 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 2 + Candidate 9 > Candidate 8 > Candidate 3 > Candidate 6 > Candidate 7 > Candidate 2 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 10 * 2 + Candidate 9 > Candidate 8 > Candidate 4 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 2 + Candidate 9 > Candidate 10 > Candidate 2 > Candidate 3 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 2 + Candidate 10 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 2 + Candidate 10 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 2 + Candidate 10 > Candidate 6 > Candidate 1 > Candidate 8 > Candidate 3 > Candidate 5 > Candidate 2 = Candidate 4 = Candidate 7 = Candidate 9 * 2 + Candidate 10 > Candidate 9 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 * 2 + Candidate 1 > Candidate 2 > Candidate 3 > Candidate 7 > Candidate 8 > Candidate 9 > Candidate 5 > Candidate 6 > Candidate 10 > Candidate 4 * 1 + Candidate 1 > Candidate 2 > Candidate 4 > Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 2 > Candidate 4 > Candidate 3 > Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 2 > Candidate 4 > Candidate 5 > Candidate 8 > Candidate 10 > Candidate 3 = Candidate 6 = Candidate 7 = Candidate 9 * 1 + Candidate 1 > Candidate 2 > Candidate 5 > Candidate 3 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 4 > Candidate 8 > Candidate 6 > Candidate 5 > Candidate 7 > Candidate 9 > Candidate 10 > Candidate 2 * 1 + Candidate 1 > Candidate 3 > Candidate 4 > Candidate 9 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 4 > Candidate 9 > Candidate 5 > Candidate 6 > Candidate 10 > Candidate 2 > Candidate 8 > Candidate 7 * 1 + Candidate 1 > Candidate 3 > Candidate 5 > Candidate 2 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 5 > Candidate 9 > Candidate 2 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 6 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 7 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 7 > Candidate 8 > Candidate 10 > Candidate 9 > Candidate 2 > Candidate 6 > Candidate 4 > Candidate 5 * 1 + Candidate 1 > Candidate 3 > Candidate 7 > Candidate 9 > Candidate 2 > Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 8 > Candidate 4 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 8 > Candidate 9 > Candidate 10 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 * 1 + Candidate 1 > Candidate 3 > Candidate 9 > Candidate 8 > Candidate 4 > Candidate 2 > Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 1 > Candidate 3 > Candidate 9 > Candidate 10 > Candidate 7 > Candidate 8 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 * 1 + Candidate 1 > Candidate 4 > Candidate 3 > Candidate 6 > Candidate 8 > Candidate 5 > Candidate 2 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 4 > Candidate 3 > Candidate 6 > Candidate 10 > Candidate 2 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 1 > Candidate 4 > Candidate 5 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 4 > Candidate 5 > Candidate 8 > Candidate 10 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 9 * 1 + Candidate 1 > Candidate 4 > Candidate 5 > Candidate 9 > Candidate 8 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 1 > Candidate 4 > Candidate 8 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 4 > Candidate 8 > Candidate 7 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 4 > Candidate 9 > Candidate 10 > Candidate 6 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 7 = Candidate 8 * 1 + Candidate 1 > Candidate 4 > Candidate 10 > Candidate 5 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 1 > Candidate 5 > Candidate 4 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 5 > Candidate 4 > Candidate 9 > Candidate 2 > Candidate 3 > Candidate 7 > Candidate 6 > Candidate 8 > Candidate 10 * 1 + Candidate 1 > Candidate 6 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 6 > Candidate 5 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 7 > Candidate 9 > Candidate 6 > Candidate 2 > Candidate 3 > Candidate 4 = Candidate 5 = Candidate 8 = Candidate 10 * 1 + Candidate 1 > Candidate 7 > Candidate 9 > Candidate 8 > Candidate 3 > Candidate 4 > Candidate 6 > Candidate 10 > Candidate 5 > Candidate 2 * 1 + Candidate 1 > Candidate 7 > Candidate 9 > Candidate 8 > Candidate 6 > Candidate 3 > Candidate 2 > Candidate 10 > Candidate 4 > Candidate 5 * 1 + Candidate 1 > Candidate 8 > Candidate 3 > Candidate 4 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 1 > Candidate 8 > Candidate 3 > Candidate 9 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 1 > Candidate 8 > Candidate 6 > Candidate 4 > Candidate 2 > Candidate 3 > Candidate 5 > Candidate 10 > Candidate 9 > Candidate 7 * 1 + Candidate 1 > Candidate 8 > Candidate 9 > Candidate 3 > Candidate 4 > Candidate 2 > Candidate 6 > Candidate 5 > Candidate 7 > Candidate 10 * 1 + Candidate 1 > Candidate 8 > Candidate 9 > Candidate 10 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 * 1 + Candidate 1 > Candidate 9 > Candidate 2 > Candidate 4 > Candidate 8 > Candidate 10 > Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 * 1 + Candidate 1 > Candidate 9 > Candidate 4 > Candidate 3 > Candidate 8 > Candidate 10 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 * 1 + Candidate 1 > Candidate 9 > Candidate 4 > Candidate 7 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 1 > Candidate 9 > Candidate 7 > Candidate 4 > Candidate 5 > Candidate 2 > Candidate 10 > Candidate 8 > Candidate 6 > Candidate 3 * 1 + Candidate 1 > Candidate 9 > Candidate 7 > Candidate 6 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 8 = Candidate 10 * 1 + Candidate 1 > Candidate 9 > Candidate 8 > Candidate 3 > Candidate 10 > Candidate 2 > Candidate 4 > Candidate 5 > Candidate 6 > Candidate 7 * 1 + Candidate 1 > Candidate 9 > Candidate 10 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 1 > Candidate 9 > Candidate 10 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 1 > Candidate 9 > Candidate 10 > Candidate 4 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 1 > Candidate 9 > Candidate 10 > Candidate 4 > Candidate 7 > Candidate 3 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 8 * 1 + Candidate 1 > Candidate 9 > Candidate 10 > Candidate 4 > Candidate 8 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 * 1 + Candidate 1 > Candidate 9 > Candidate 10 > Candidate 8 > Candidate 4 > Candidate 3 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 * 1 + Candidate 1 > Candidate 10 > Candidate 3 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 1 > Candidate 10 > Candidate 5 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 1 > Candidate 10 > Candidate 9 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 1 > Candidate 10 > Candidate 9 > Candidate 2 > Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 1 > Candidate 10 > Candidate 9 > Candidate 4 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 2 > Candidate 3 > Candidate 1 > Candidate 4 > Candidate 9 > Candidate 10 > Candidate 8 > Candidate 5 > Candidate 6 > Candidate 7 * 1 + Candidate 2 > Candidate 3 > Candidate 9 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 2 > Candidate 4 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 2 > Candidate 4 > Candidate 9 > Candidate 7 > Candidate 3 > Candidate 8 > Candidate 10 > Candidate 6 > Candidate 1 > Candidate 5 * 1 + Candidate 2 > Candidate 5 > Candidate 3 > Candidate 1 > Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 2 > Candidate 7 > Candidate 9 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 2 > Candidate 8 > Candidate 9 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 2 > Candidate 8 > Candidate 9 > Candidate 4 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 2 > Candidate 8 > Candidate 10 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 * 1 + Candidate 2 > Candidate 9 > Candidate 1 > Candidate 10 > Candidate 4 > Candidate 3 > Candidate 8 > Candidate 5 = Candidate 6 = Candidate 7 * 1 + Candidate 2 > Candidate 9 > Candidate 7 > Candidate 4 > Candidate 8 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 2 > Candidate 9 > Candidate 7 > Candidate 8 > Candidate 6 > Candidate 10 > Candidate 5 > Candidate 4 > Candidate 3 > Candidate 1 * 1 + Candidate 3 > Candidate 1 > Candidate 2 > Candidate 4 > Candidate 5 > Candidate 6 > Candidate 7 > Candidate 8 > Candidate 9 > Candidate 10 * 1 + Candidate 3 > Candidate 1 > Candidate 5 > Candidate 2 > Candidate 4 > Candidate 7 > Candidate 6 > Candidate 9 > Candidate 8 > Candidate 10 * 1 + Candidate 3 > Candidate 1 > Candidate 6 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 3 > Candidate 1 > Candidate 8 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 3 > Candidate 4 > Candidate 1 > Candidate 9 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 3 > Candidate 4 > Candidate 5 > Candidate 8 > Candidate 10 > Candidate 1 > Candidate 2 > Candidate 9 > Candidate 6 > Candidate 7 * 1 + Candidate 3 > Candidate 4 > Candidate 9 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 3 > Candidate 4 > Candidate 10 > Candidate 1 > Candidate 6 > Candidate 2 > Candidate 5 > Candidate 8 > Candidate 9 > Candidate 7 * 1 + Candidate 3 > Candidate 5 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 3 > Candidate 6 > Candidate 1 = Candidate 2 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 3 > Candidate 7 > Candidate 1 > Candidate 5 > Candidate 2 = Candidate 4 = Candidate 6 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 3 > Candidate 8 > Candidate 2 > Candidate 7 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 9 = Candidate 10 * 1 + Candidate 3 > Candidate 9 > Candidate 1 > Candidate 8 > Candidate 7 > Candidate 5 > Candidate 4 > Candidate 2 = Candidate 6 = Candidate 10 * 1 + Candidate 3 > Candidate 9 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 4 > Candidate 1 > Candidate 3 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 4 > Candidate 1 > Candidate 5 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 4 > Candidate 2 > Candidate 7 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 4 > Candidate 3 > Candidate 1 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 4 > Candidate 3 > Candidate 1 > Candidate 2 > Candidate 9 > Candidate 10 > Candidate 8 > Candidate 6 > Candidate 7 > Candidate 5 * 1 + Candidate 4 > Candidate 3 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 4 > Candidate 3 > Candidate 8 > Candidate 1 > Candidate 10 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 * 1 + Candidate 4 > Candidate 3 > Candidate 8 > Candidate 9 > Candidate 1 = Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 4 > Candidate 5 > Candidate 1 > Candidate 8 > Candidate 9 > Candidate 10 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 * 1 + Candidate 4 > Candidate 5 > Candidate 7 > Candidate 10 > Candidate 1 > Candidate 2 > Candidate 8 > Candidate 3 = Candidate 6 = Candidate 9 * 1 + Candidate 4 > Candidate 5 > Candidate 8 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 4 > Candidate 5 > Candidate 9 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 4 > Candidate 7 > Candidate 10 > Candidate 3 > Candidate 8 > Candidate 1 > Candidate 2 > Candidate 6 > Candidate 5 > Candidate 9 * 1 + Candidate 4 > Candidate 8 > Candidate 1 > Candidate 3 > Candidate 5 > Candidate 6 > Candidate 10 > Candidate 2 > Candidate 9 > Candidate 7 * 1 + Candidate 4 > Candidate 9 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 4 > Candidate 9 > Candidate 2 > Candidate 7 > Candidate 3 > Candidate 5 > Candidate 10 > Candidate 6 > Candidate 1 > Candidate 8 * 1 + Candidate 4 > Candidate 9 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 4 > Candidate 10 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 4 > Candidate 10 > Candidate 3 > Candidate 1 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 4 > Candidate 10 > Candidate 5 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 4 > Candidate 10 > Candidate 9 > Candidate 3 > Candidate 1 > Candidate 2 > Candidate 5 > Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 5 > Candidate 8 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 5 > Candidate 10 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 6 > Candidate 3 > Candidate 4 > Candidate 1 > Candidate 2 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 6 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 6 > Candidate 7 > Candidate 4 > Candidate 10 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 5 = Candidate 8 = Candidate 9 * 1 + Candidate 6 > Candidate 8 > Candidate 9 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 10 * 1 + Candidate 6 > Candidate 9 > Candidate 2 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 6 > Candidate 9 > Candidate 10 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 7 * 1 + Candidate 7 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 7 > Candidate 1 > Candidate 8 > Candidate 9 > Candidate 10 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 * 1 + Candidate 7 > Candidate 1 > Candidate 9 > Candidate 4 > Candidate 3 > Candidate 8 > Candidate 10 > Candidate 2 = Candidate 5 = Candidate 6 * 1 + Candidate 7 > Candidate 2 > Candidate 6 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 7 > Candidate 2 > Candidate 9 > Candidate 6 > Candidate 8 > Candidate 3 > Candidate 10 > Candidate 5 > Candidate 1 > Candidate 4 * 1 + Candidate 7 > Candidate 2 > Candidate 9 > Candidate 8 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 7 > Candidate 2 > Candidate 9 > Candidate 8 > Candidate 4 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 7 > Candidate 3 > Candidate 4 > Candidate 1 = Candidate 2 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 9 = Candidate 10 * 1 + Candidate 7 > Candidate 4 > Candidate 1 > Candidate 2 > Candidate 3 > Candidate 5 > Candidate 6 > Candidate 8 > Candidate 9 > Candidate 10 * 1 + Candidate 7 > Candidate 4 > Candidate 2 > Candidate 9 > Candidate 8 > Candidate 1 > Candidate 3 > Candidate 6 > Candidate 10 > Candidate 5 * 1 + Candidate 7 > Candidate 6 > Candidate 9 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 8 = Candidate 10 * 1 + Candidate 7 > Candidate 8 > Candidate 3 > Candidate 1 = Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 9 = Candidate 10 * 1 + Candidate 7 > Candidate 9 > Candidate 2 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 7 > Candidate 9 > Candidate 6 > Candidate 8 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 10 * 1 + Candidate 7 > Candidate 10 > Candidate 1 > Candidate 4 > Candidate 3 > Candidate 8 > Candidate 9 > Candidate 2 = Candidate 5 = Candidate 6 * 1 + Candidate 8 > Candidate 1 > Candidate 5 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 8 > Candidate 1 > Candidate 9 > Candidate 4 > Candidate 3 > Candidate 2 > Candidate 10 > Candidate 5 > Candidate 6 > Candidate 7 * 1 + Candidate 8 > Candidate 2 > Candidate 7 > Candidate 3 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 9 = Candidate 10 * 1 + Candidate 8 > Candidate 4 > Candidate 2 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 = Candidate 10 * 1 + Candidate 8 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 9 = Candidate 10 * 1 + Candidate 8 > Candidate 7 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 9 = Candidate 10 * 1 + Candidate 8 > Candidate 7 > Candidate 9 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 8 > Candidate 7 > Candidate 9 > Candidate 10 > Candidate 6 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 * 1 + Candidate 8 > Candidate 9 > Candidate 1 > Candidate 2 > Candidate 7 > Candidate 4 > Candidate 3 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 8 > Candidate 9 > Candidate 2 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 8 > Candidate 10 > Candidate 3 > Candidate 1 > Candidate 2 > Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 9 * 1 + Candidate 8 > Candidate 10 > Candidate 7 > Candidate 6 > Candidate 9 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 * 1 + Candidate 8 > Candidate 10 > Candidate 9 > Candidate 7 > Candidate 6 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 * 1 + Candidate 9 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 1 > Candidate 3 > Candidate 4 > Candidate 7 > Candidate 10 > Candidate 8 > Candidate 6 > Candidate 2 > Candidate 5 * 1 + Candidate 9 > Candidate 1 > Candidate 4 > Candidate 3 > Candidate 7 > Candidate 8 > Candidate 2 > Candidate 10 > Candidate 6 > Candidate 5 * 1 + Candidate 9 > Candidate 1 > Candidate 7 > Candidate 3 > Candidate 8 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 9 > Candidate 1 > Candidate 8 > Candidate 3 > Candidate 4 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 9 > Candidate 2 > Candidate 7 > Candidate 3 > Candidate 4 > Candidate 5 > Candidate 6 > Candidate 8 > Candidate 1 > Candidate 10 * 1 + Candidate 9 > Candidate 2 > Candidate 7 > Candidate 4 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 2 > Candidate 7 > Candidate 8 > Candidate 3 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 9 > Candidate 2 > Candidate 7 > Candidate 8 > Candidate 6 > Candidate 10 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 * 1 + Candidate 9 > Candidate 2 > Candidate 10 > Candidate 6 > Candidate 5 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 7 = Candidate 8 * 1 + Candidate 9 > Candidate 3 > Candidate 1 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 3 > Candidate 4 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 4 > Candidate 1 > Candidate 2 > Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 4 > Candidate 8 > Candidate 1 > Candidate 2 > Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 9 > Candidate 4 > Candidate 8 > Candidate 1 > Candidate 3 > Candidate 7 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 9 > Candidate 6 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 6 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 6 > Candidate 7 > Candidate 2 > Candidate 3 > Candidate 4 > Candidate 5 > Candidate 1 > Candidate 8 > Candidate 10 * 1 + Candidate 9 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 7 > Candidate 2 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 7 > Candidate 2 > Candidate 1 > Candidate 5 > Candidate 3 = Candidate 4 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 7 > Candidate 2 > Candidate 6 > Candidate 8 > Candidate 4 > Candidate 1 = Candidate 3 = Candidate 5 = Candidate 10 * 1 + Candidate 9 > Candidate 7 > Candidate 2 > Candidate 10 > Candidate 3 > Candidate 1 > Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 * 1 + Candidate 9 > Candidate 7 > Candidate 3 > Candidate 1 > Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 = Candidate 10 * 1 + Candidate 9 > Candidate 7 > Candidate 10 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 * 1 + Candidate 9 > Candidate 8 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 10 * 1 + Candidate 9 > Candidate 8 > Candidate 3 > Candidate 7 > Candidate 1 = Candidate 2 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 9 > Candidate 8 > Candidate 4 > Candidate 1 > Candidate 3 > Candidate 10 > Candidate 2 = Candidate 5 = Candidate 6 = Candidate 7 * 1 + Candidate 9 > Candidate 8 > Candidate 6 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 7 = Candidate 10 * 1 + Candidate 9 > Candidate 8 > Candidate 6 > Candidate 3 > Candidate 2 > Candidate 7 > Candidate 10 > Candidate 1 > Candidate 4 > Candidate 5 * 1 + Candidate 9 > Candidate 8 > Candidate 7 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 9 > Candidate 8 > Candidate 7 > Candidate 2 > Candidate 1 > Candidate 10 > Candidate 3 > Candidate 4 > Candidate 6 > Candidate 5 * 1 + Candidate 9 > Candidate 8 > Candidate 7 > Candidate 2 > Candidate 3 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 10 * 1 + Candidate 9 > Candidate 10 > Candidate 2 > Candidate 6 > Candidate 7 > Candidate 8 > Candidate 4 > Candidate 5 > Candidate 1 > Candidate 3 * 1 + Candidate 9 > Candidate 10 > Candidate 7 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 * 1 + Candidate 9 > Candidate 10 > Candidate 7 > Candidate 4 > Candidate 1 > Candidate 2 > Candidate 6 > Candidate 3 > Candidate 5 > Candidate 8 * 1 + Candidate 10 > Candidate 1 > Candidate 3 > Candidate 4 > Candidate 2 > Candidate 5 > Candidate 6 > Candidate 9 > Candidate 8 > Candidate 7 * 1 + Candidate 10 > Candidate 1 > Candidate 9 > Candidate 2 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 10 > Candidate 3 > Candidate 5 > Candidate 1 = Candidate 2 = Candidate 4 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 10 > Candidate 4 > Candidate 1 = Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 10 > Candidate 4 > Candidate 1 > Candidate 2 = Candidate 3 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 = Candidate 9 * 1 + Candidate 10 > Candidate 4 > Candidate 1 > Candidate 9 > Candidate 5 > Candidate 3 > Candidate 2 > Candidate 6 > Candidate 7 > Candidate 8 * 1 + Candidate 10 > Candidate 4 > Candidate 9 > Candidate 1 > Candidate 3 > Candidate 2 > Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 10 > Candidate 7 > Candidate 8 > Candidate 4 > Candidate 5 > Candidate 1 > Candidate 2 > Candidate 9 > Candidate 6 > Candidate 3 * 1 + Candidate 10 > Candidate 9 > Candidate 2 > Candidate 1 = Candidate 3 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 7 = Candidate 8 * 1 + Candidate 10 > Candidate 9 > Candidate 4 > Candidate 8 > Candidate 6 > Candidate 3 > Candidate 1 = Candidate 2 = Candidate 5 = Candidate 7 * 1 + Candidate 10 > Candidate 9 > Candidate 7 > Candidate 3 > Candidate 2 > Candidate 1 = Candidate 4 = Candidate 5 = Candidate 6 = Candidate 8 * 1 + EOD, + $election->getVotesListAsString() + ); + + self::assertSame('Candidate 1 > Candidate 9 > Candidate 8', $election->getResult('STV')->getResultAsString()); + } + + public function testBugDavidHillRandomOrderAndStatsRound(): void + { + $hil = new DavidHillFormat(__DIR__.'/TidemanData/A60.HIL'); + + self::assertEquals([0=>'1', 1=>'2', 2=>'3', 3=>'4', 4=>'5', 5=>'6'], $hil->candidates); # Candidates are object, AssertEquals compare __toString + + $implicitElectionFromHill = $hil->setDataToAnElection(); + + // Without aggregate vote + $file = new \SplTempFileObject; + $file->fwrite(CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $implicitElectionFromHill, aggregateVotes: false)); + $implicitElectionFromCondorcetElection = (new CondorcetElectionFormat($file))->setDataToAnElection(); + + self::assertEquals($implicitElectionFromHill->getCandidatesListAsString(), $implicitElectionFromCondorcetElection->getCandidatesListAsString()); + + foreach (Condorcet::getAuthMethods() as $method) { + // Stats + self::assertSame($implicitElectionFromHill->getResult($method)->getStats(), $implicitElectionFromCondorcetElection->getResult($method)->getStats(), 'Method: '.$method); + + // Result + self::assertSame($implicitElectionFromHill->getResult($method)->getResultAsString(), $implicitElectionFromCondorcetElection->getResult($method)->getResultAsString(), 'Method: '.$method); + } + + + // With aggregate vote + $file = new \SplTempFileObject; + $file->fwrite(CondorcetElectionFormat::exportElectionToCondorcetElectionFormat(election: $implicitElectionFromHill, aggregateVotes: true)); + $implicitElectionFromCondorcetElection = (new CondorcetElectionFormat($file))->setDataToAnElection(); + + self::assertEquals($implicitElectionFromHill->getCandidatesListAsString(), $implicitElectionFromCondorcetElection->getCandidatesListAsString()); + + foreach (Condorcet::getAuthMethods() as $method) { + // Stats + self::assertEqualsWithDelta( + $implicitElectionFromHill->getResult($method)->getStats(), + $implicitElectionFromCondorcetElection->getResult($method)->getStats(), + 1 / (0.1 ** Condorcet::getMethodClass($method)::DECIMAL_PRECISION), + 'Method: '.$method + ); + + // Result + self::assertSame($implicitElectionFromHill->getResult($method)->getResultAsString(), $implicitElectionFromCondorcetElection->getResult($method)->getResultAsString(), 'Method: '.$method); + } + } +} diff --git a/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2006_tally.txt b/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2006_tally.txt new file mode 100644 index 0000000..ff5a562 --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2006_tally.txt @@ -0,0 +1,445 @@ + + Tally Sheet for the votes cast. + + The format is: + "V: vote MD5SUM" + You may locate the MD5SUM corresponding to a debian login ID, + given the associated secret token returned with the ack, + by simply running md5sum. Forexample, for login ID srivasta, and + the secret token 0123456789ABCDE, the following invocation works: + % echo "srivasta 0123456789ABCDE" | md5sum + The vote block represents the ranking given to each of the + candidates by the voter. + -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + Option 1--------->: Jeroen van Wolffelaar + / Option 2-------->: Ari Pollak + |/ Option 3------->: Steve McIntyre + ||/ Option 4------>: Anthony Towns + |||/ Option 5----->: Andreas Schuldei + ||||/ Option 6---->: Jonathan aka Ted Walther + |||||/ Option 7--->: Bill Allombert + ||||||/ Option 8-->: None of the Above + |||||||/ +V: 57213846 1d7ba886e62a2cf64379a5c8f8032cc2 +V: 3-311-56 5983810b34693310479f3b26eda85177 +V: 16313857 1ccb15e79dc5734b217fb8e3fb296b9d +V: 47125863 52b444e0fb8778d31d52efb6b6e88401 +V: 2-1----3 b2c77c75a0747fc1b6019ca54d76aa42 +V: -------1 21acf319d1a9185b36fa065c649ca1da +V: --21---- 5778a0e6c4c15de35a92c85c66e1ecff +V: 47321856 9b76679e424a887ccf80f506ab8ba81c +V: 26145837 fdab256bb874774d3a67547f88f23ffc +V: 1-423-5- b71d5f0a6e58b4e6ec2d81bce6160799 +V: 43516728 29a8468f09b317cea6b6c3defdccb513 +V: 38214756 b13ae01b3143d7287e099f3746e80211 +V: 4-123-4- 321ab097081bea5d4ee967f670fe9998 +V: 18234756 df985c5e22db36646fd9e6ceb594cefc +V: --1----2 0ffef79d2a2f58928e677160ffa6eaaf +V: 31323735 64fe866fc009aa01fddff32268b6d0fe +V: 47134826 205d5eea98ef53d8fdc77c04a5a55a08 +V: 36124857 cc9da13d222a4e987f3dcc1286e86827 +V: 63472815 0c7f3ca5b9d12d159428aaa3c28b319c +V: 36172845 38957e1d01d3bd1798f21d8ede7570f2 +V: 56314827 15251ab0ced1f920959a4fe6edcc4bc4 +V: 47135826 69b76a672ecc2038ff585f19129330dd +V: --213--- 618ae0bdab9d6c79a9fdb01636c579b2 +V: 16432735 be3d804ecf4b6d120b05acf9a51410b5 +V: ----2--1 7aa0f0fb4f7c64ae913ab0e56066d4cd +V: 17244826 25f689d866a88ee2a9759568fd3fe005 +V: 36213837 5cdaab4305428a8ca58a3cbfe59b95b3 +V: 48213756 06d4fb088c5f5ab2f64c18a34431b745 +V: 25331746 e365550e0d6b3b06e7ee847cadf96d76 +V: 68456837 def821da56979f9a5343e8794c7357ee +V: --21---3 d5ec4a853775b7fa030c6b75145d03b3 +V: 45213867 215d5062f2fcf07a9d59c35dcdda3a0d +V: 3-12---4 edb43166e6c262a6ac65d292187449d4 +V: 27413856 a0cb8268f78214afec42e6553ee02d2e +V: 26314857 4ce63b670a52419f8049c5e8f48f2374 +V: 33313332 6a8df1962f5c4e3366670b09cf584e49 +V: 16341857 652d3d17e3f31a54c6cac6bccc7bc88f +V: 12437746 0fdfbdf716e0ad87f34c8dc36f0dd787 +V: ------1- add8d5d6a42dd64dabda529e5f1164a1 +V: 35182764 34364576fff96cc923019d044992dc4f +V: 25143867 572294825056c43dd29e7ceec5527807 +V: 37142856 a05bdb2c64d2cb6c815da770610dcf5a +V: 26434415 1df47bf79ec9072715c1f02becca192a +V: 17172776 6be1de6c656087ef4e3cddec0ca4f721 +V: 22111423 11c02dd825a181522f347b6f01f3d8cd +V: 34313-45 5cac77e9526f23ce4a1a16abb26db617 +V: 17722877 8cab4abee238fa92f915cad6c207d015 +V: -1-2---3 8e7d53af5d79a4f02e3b953ddf6a90a7 +V: 2512143- 9b8e13faa9eee964c4d036d6d2026cd8 +V: 23222514 4da78e380dd0348cc23b08d202b06b07 +V: 35176824 04941b0b82c4ad0ecaa0bd3fabb99747 +V: 17245836 40436efc84e638cb88ed622cf391ac95 +V: 3-21465- c20db1801a0ce83d3a1708284fbf7d41 +V: 26143857 79da07c69774e5c02f4e965acfa12659 +V: 56125847 13466aa166e8bd33ecdd22225732b46f +V: 37124856 6ef35eb5863c7622f262b63762b06785 +V: 35124876 51eebd2937a95059c8f412ec1f65d37a +V: 34217888 64b30ef5c9727a7a5eb97188e853cce7 +V: 36214857 3486cec2aa47163e4c31cbfdd3cd8c18 +V: 32143615 4d559753e30e204760c1173e07bc12bc +V: 47113865 b72b0c7346dfb180fe295b9c983b8e06 +V: 51426837 d0bfe4b03c6987e34cab3b1f88d38d68 +V: 35187624 11747aa6d4fbdd0bc1c502abd5cc0420 +V: 17365824 39c0873fb3ea788c2e784b7a59de432e +V: 27314756 6c0e7bb7ed471b66889ae35209a0f65c +V: -------1 2fcb54e96e72615d584d25a764bb5bdf +V: 57241836 a59dd95cdf8f9320fb4316a0bd302b12 +V: 16235874 bcf0c7f540325d0565c55d2bc46e7d00 +V: 57463841 c7f910e0cdc01d325af5b4250154829a +V: 33351432 0c2c1c6a578b0d200e4043e51db02810 +V: --21---- 2e7d77538990c1e31d13b741940a4d02 +V: 53412756 943d20eb0ed12484d06d2d021b7d459f +V: 84516732 f658fcba86c86801db4ad3ea978b7761 +V: 62417853 d8ec1af3f585b8406f61540f0e66d04c +V: 67854123 f5e5faa5d9b53a8dab2adcdc0986e179 +V: 13131312 94d43e38f7c76a6d5524d247a2f45656 +V: 52466813 348339ee1016d2f9411784048cba11f9 +V: 36214857 9885e78df0246087d547089844cc9e8a +V: 1--12--- dd6b581b2a8f63cdd34653d489356195 +V: --213--- 0ae4501dc5ce4048066354d5c3d4af7d +V: 18321854 69edd0c955e9bc8fa84ac20a77e20e20 +V: -1-23-4- 262face2bb4273375e9fd76c6a2509e5 +V: 38214627 1b3a3bdbd0a179980483e416e0795baa +V: --2---13 46bc6540158fc0daec9c70cf5bd82d84 +V: 17263845 f176ab1b4ae7b5e58dac00e245e03d48 +V: 13212534 bbe25dc3ce2685837c519f081ea64b2f +V: 25413768 c8a8f8868387fe1c329d3ae032db683f +V: 16323837 c1f1fe56c798749ee68b66cbefe622fe +V: ------1- f243ecb479123642450e3dbda3206fbf +V: 36217854 fd6d9b3830630169e825007f13ebe8bf +V: 1312243- d981046fdf12bc06d3323f4818b4ecf8 +V: 56123748 526319ee2e62ce669f3c423d19db5253 +V: ----1--2 b612cf7fa381d20e570a6ffe96c69c1f +V: 36215847 ce1ce9b1c098572142b1e14267f7119d +V: 63571824 2b78c2b02dd6ef88c3cbf08bf34b6ba9 +V: --213--4 2e6cf5d0919e44bab5da343986d4bdaa +V: 47312658 84ae6893f6d9ac51da736a8fd218d5ee +V: 68324751 89cdcd02e654e8a01c3bc22339b25c2a +V: 18327564 203b053779120f7f124741e55d4b6b29 +V: 27145863 9b780b86648711a1a6c88ed6062ebe58 +V: 27222816 2c8f95554a9870c50b5cf3efb76ce7d7 +V: 35231654 ffd9b278ed3fc7fa8c4035f36b37bb6a +V: 53764812 930472fe08a877860f3bd36f9732866d +V: 76281543 a47a0563c7c4bc859513d9ec77b0984c +V: --31--24 34d620519358d03577e5f4ecfa085193 +V: 1--23--- 89273c0a7ef50e10c75bac6225f3bca5 +V: 36214758 7d7a168f75df077736c4c8b5b16bbf8a +V: ---1--23 0566a2deea82f48a34909f42ae9c1d2f +V: 37241856 bd91b538b63952989e69a4e741d267f3 +V: 37862415 141aa11e71c9d7f13fd8581d6d121683 +V: 35126847 87cd81732ba07d494b1ad21c416e3c53 +V: 66555847 fed9917aad1b843920ab9e0fb6a76432 +V: -------1 a0d57e402df787ea900f5eeece60f3e8 +V: 56283817 699d933ac9270b5c0899393464e23445 +V: ---21--3 a92e12939d92e230cc4dcd7ac3b7500f +V: ----1--2 393df9cd1a4acd036136e1fc18a0c202 +V: 47213856 1b3e06fed30a0d10951c3bd6e23e684b +V: 27314865 075af50b08f18d3fe215f462a8c40186 +V: 23212423 443fd55c64badfef00ebfda88060560c +V: 2-15---- bb4f3af2fab5aebe4040ca0b744d8bc6 +V: --1---23 93326e4606370bcce0447aa5ce18ba26 +V: 14325867 2e2226b7b3b0149cb44cc35b8197240c +V: 48213756 75859a51ce1826198d24cd3ff31e3405 +V: 54137826 7ebf1d0373d3e34891dfcc755b965b10 +V: 57123846 13bb5bb80516771d4ae60dd1a16124bb +V: 3-2-4-15 340999c0ec581eddc1d6acba03f969a4 +V: 1-121-2- b6952712bcfc67e75d9c782d96843e07 +V: 54312678 58e6eeed6d3a00cb716cc87b7db3cbfb +V: 56281743 ba1699941ee575f6e854e62ec5007945 +V: 3651246- 290a2e2c02659c0ec9ab08a2d228bd50 +V: ---1---2 de1f95854235d0483efc9ba459e2d155 +V: 47214836 74898729967bb36fd06baa6c5dbbf901 +V: 37214856 c92e606be125ec4d1d5348223c021933 +V: 35271846 481a1776d8ec86ec99298e18464ae19d +V: 47235816 66956aa6b91cc91bbce99a525643a438 +V: 26143857 d4ca50a32d95adcbe73d15b846e5a63a +V: 36512847 774848bf48af2111487fb965a3be58d6 +V: 12576834 2551e91a8e7668cbb0f482ccc10f32ed +V: 57214836 a0f24ccf79099b4bac8832749743f2be +V: 47123658 4d0c9446881c4754e30bc6b3bb3fad0b +V: 46132857 d0d775364c3a7da51432c79ec8387bf1 +V: 35512554 b64c8d26c985b4703b63f55221828d2e +V: 75261843 3eaba62f35ffa84dfe02ed2e90370ba3 +V: 4-123-56 71255bd645f2f58654624ead16febd52 +V: 3651364- eb77b4378a3ffb30e767d6e6f0b41509 +V: 17235846 04c2bd919aabbc157d74dda7e91b6bdd +V: 37125846 00711d1fbb56afd0595c77ff5dcfd988 +V: 17245836 4a12429299445d2bf6fd5436910f75f5 +V: 34172856 37af528b7e9a225e32477d730bdcfe59 +V: 45212867 587fa0665ae596a95d7ece13e2ce22cc +V: 1--32--4 3d51934b51002a07f91417cf34d2c554 +V: 56314827 0f761b0ce435d6ce62d64b3e22aba016 +V: 1------2 16c3c50d7d6b5328531543a784d54aae +V: 37124856 7cef1bfed93d560db0f7e586c036d63e +V: 36214758 9bc3d5b8ab15177cf1faacda4dd2f426 +V: 47312865 fed32a2c4312d80a1435f0c87413d924 +V: 34213645 f1475c21940d4e495f12a3627d81c640 +V: --2-1--- 8612593f5f075ad538bebb8165af866f +V: 47123856 0c3e2026ceeaf3bb82ecc8b83f1b5441 +V: 36415872 684beceb9005f15e19ed8c95a6af19f8 +V: 27154836 622a15b70c1d7ea1e7025072a2a7e2cd +V: 14121113 561b5f127729b6e0e562218fbb540e37 +V: 2---1--- 354690c06e2ee7233cdcd8cab7dbcef0 +V: 4351267- 0efe4848150319dfe4b4524d9f35dd74 +V: 3----2-- aab4b685acc5e43beb6f8d98169a10f2 +V: 65173842 1ae6b329601fbff1b14a8b51f0729238 +V: 25361-74 722e6a02a32db6af4d96f2d5d78b5c10 +V: 36174258 9572bca2548943135baf0cebea660970 +V: 21222827 d4d735f042dec1bd930e08049675cba6 +V: 18234756 d993813864820064f475cde6cff78bb0 +V: 1--32--4 4dfad67ff5f70d006506079f9cda1498 +V: 24121513 b2a922593853e7dde04ec157b5eb2a22 +V: 3714265- 505ad37f22f3aeacb75e1c80ad4e98a2 +V: 18311876 3f34c5e7b1f8aea7c21ff1d9791dea70 +V: 58261734 9eafa3d8c4420ad1128cb4f092e85d0d +V: -------1 5fd942211bf7a1bc00e25baedd68337d +V: 2-1-3--4 1d970b99347c3280a291c795349be841 +V: 32313534 551853e032d3af216766cf5ce628b048 +V: 33313332 af6f53e4eecb7025fc1a2aaf56bbc8ea +V: 47213856 6b5b8478d87ea4cccb5d8bb47598c7fc +V: 76372851 e9c7cbd222a7208679e79883f086c980 +V: 67435812 77f7d77ae0c49c073bd10ca29972341f +V: 2634175- 2dde0c5c869860ab65fbf9a1989a4fae +V: 15342746 04d00c5ac8f72303e41ce4aed80031de +V: 77182883 e51f673ed77a5eeb528ca6fcf9d8ccf6 +V: ---12-3- 3e634a2cd14108e31728ec7297633408 +V: 1111111- a2ddcdf092cf246bb6eebb19d49b735d +V: 28211756 7fcb58961f970342e55ada51e4ecdf3f +V: 26315748 5e3ec904295fbd5630c752f0de8cd691 +V: 22222321 da73a4f7b6d4a13ce0c3d05588bb236a +V: 3--21--- 606507e3764d93396765c376d9da1742 +V: 45263718 383bf17a69d06d7a5a21f54aac301802 +V: 46132857 e23ca002e58c630692b28dffa1ca297a +V: 56214837 20069e176e4483f8da1e51a0b6123867 +V: 21233837 e414567128a8f008ab77e198e1cc3f29 +V: 3--1-2-- 02330a44c97af838a81d6292573b4eda +V: 47312756 c8c545e83d619d2114c0fbccd7b2958d +V: 16243857 81a34863eddb1ace447f03e0ee8c5ed5 +V: 3321142- 471075a14f0204d43966bc7ed1836380 +V: 27124856 1f090b828641f58f23077ed9e63dfb9a +V: 15252436 cdcce2c16bb4f2c65e021adf8189ca54 +V: 13542756 c282080f91976a8fe8705e95304dbaae +V: 24316857 6f3d994c2b76ad7a3394bc780d7773f9 +V: 46315827 1a6ce57f379a856ae765619df2d11c9e +V: 16213754 bb36796a34d9d3018d43c00f5521877b +V: 43412546 8710ae17f79d0f7ca6f5867d6aa5cf1b +V: 14121413 76f747f526e28529b540f767b3d7ae86 +V: 37122856 181472faa895b41286461d7547d71b66 +V: 3---1-2- a8d023d434d82734f3a5a80d04a1940c +V: 46132785 36b8cae2b98fc3db9eaf959f44311cad +V: 45123716 5f9a087e69c3bc94d0aaa7346dfa4785 +V: 57218634 1a1a17ae3c0c5e477cc7bb9a49460610 +V: 2-134--- a43c376a51775634062ca0d3e6fb94ea +V: 25553514 260e30bedea0c52b1bbb0a2815e6816d +V: 32313534 b91f1cd1a266f0b83a19773de41a57b2 +V: --34321- ba73f9b8cd0b382e601b0af0628ef9c7 +V: 45132768 a533eb573a0c666ebeaa75520fd8a0ad +V: 13211423 85447b4a780d6cb76155430f94ba2fc3 +V: 12232827 cea1a6ed78c00e4f6766f26d5680bc0b +V: 37114865 521f0599b9293761a8c6d5c4c9331b27 +V: 17243856 dc2f98656a8658910368c59f0dd29d64 +V: 2-314--- 73f124f4951b7fa4be929348f4f69893 +V: 2---1-3- a4f78f197aebe3725b28ad6cd81ee791 +V: 27354861 833ec1a121582f4862b3918f3c217648 +V: 3542155- 28ca97fcaa707b3da1fdbc9ff16e9dd9 +V: 27314856 551578fff90d4efa1a448ae18662b966 +V: 58213647 ee72bed75ddee0ab99928a1ec84be049 +V: 37213845 7ecd14e14664e97b71ed6fd6ae9e3289 +V: --13---- 91fdfe530a9b91bb007556beb10bff1f +V: 3-213--- 68565639059e4fc6b990d804f9233fa5 +V: -412--35 b97c6eede494b947bb16ccb17ceeb69b +V: 16342857 06d98fd115fe376920e2d2ac512aa2ca +V: --111--- 8a82b70816aaad1d467758c5812d58ef +V: 15132645 66848b8170c2db2f2abe6b25d1e5c9c0 +V: --12---- 3f065a7957d7f390a3e061b7900c6f96 +V: 25783812 29cfdb2a407276bfbe3b15b376c3011e +V: 4-352-16 d2660f925cdf7652cd288a335c36c087 +V: 46135827 f682962b778365ed60bb1c5689922ddc +V: 18243657 baaa4948103cdcfd22a35bab497498c8 +V: 35426817 ab8689d544892cd98a8934d8177071e0 +V: 37213546 1efbf206b794d5fb465fb09e03963969 +V: 48617352 3aaeacf14b78212fc8fa472c8f4cf291 +V: 46125837 df78f6e49831939364f72a3b240b9445 +V: 27163845 0b0b628b347f4e8b1eacb899b3af83c8 +V: 46123857 b4b1a3fbd7496fec1aec1b05d975ce4e +V: 16432857 267a89c4fb685ae9469a29d520501c9e +V: 2-321--- de972272af8ce405a6d182a9fd720bed +V: 36412-57 38bc1b80864acc40b59a5f3a87693a82 +V: 67241538 8509328895c9bcbcb30aaf4d77caf046 +V: 26241-35 f2a9a4977ed875007ebd63fdbbdf9c16 +V: 1642735- 2bdd09c10af28c9a7db48531e5055746 +V: 2---1-3- bc60c46c4d479dd14d2cc82aeb76ae47 +V: 58162347 f5751d54419ec48b51a3aa539c2a06f2 +V: 26431758 48e4faceb12114a34875fb10c515c822 +V: 44321325 b5e89830a69176124166c80f0b73d943 +V: 46531827 2fa36fb883943603b77ef6bf7134650a +V: 2-31---- edbce0a99d07f4a9fb0cc89c5a721adf +V: 46413827 e62a26583df4a6e9cf8327504ee1c440 +V: 64312758 0af4d0d55a6e24603b8cc2c6d171e3d8 +V: 46321857 beb78a9a14abd6efcf872f3d3eb2595a +V: 25333614 e40a9c140998105c697a3ffc81b2b18e +V: 47213856 47a15efec113b8dbcd8ea622e8ec3aa7 +V: 2222122- b01b57e2760c298b03c10098b8667bf7 +V: 34412645 c0214391f78a07db85d9a510b3088a12 +V: ---1---2 b62553d8cda24607d9dbe18d2e796636 +V: 37251846 f3ff3af00d333aa9fd7984a25ea0ea4a +V: 37241856 ef27166a1513ed44afa951b0d6aad90b +V: 4731286- c86ee3b3bf804b13420b7114d04ffd83 +V: ---1---- d89199800cbeefe076ea5e42d0d6338e +V: --32--1- a5689afcb25e65fe5558cb0f5f22699d +V: 54612738 b63567bab3d8fd1d73f39085391ac1c7 +V: 15221634 7f27ce42b144eae453bb61a6679623b1 +V: 4-124-35 9046bb5a0c4fed095c518dd8ea647cea +V: ---12435 7cb8db4791e5c0b553d0da00e8184616 +V: 36245817 89480e5e36bc818d11ad4da244c3c805 +V: 57143628 3081e523fa72f996c616dd249737d60d +V: 37241756 800a4c20e9e64739289bf4e806378988 +V: 11111817 039cfc7b5204fa3bbc8ed947957b277d +V: 2713477- d951b48292a445b1e19c1c3d167b7c78 +V: 7718287- 6444ff6d1f40b53c4cae53de0439bb92 +V: ---1---- a367913ddf67262c843a076f1ec663a8 +V: 34313334 08a9efef2a901bfa6d1cf0ece23943fb +V: ---1---- 18c3d5e7bce1aa7fd7e660db6e252bcd +V: 1------- 1b1aa1f2cc3e5f80232808ec00291fc7 +V: 31264876 555b6a094b7aa7955193ed50b8871719 +V: 26621668 114f8d914a5df96a3ae6689301f6af20 +V: 4-3125-6 3e022fc2787ac2bb6d663bc94c27cf0e +V: 57216843 0cfe14ad878eff2ab4614834bb9fd5c7 +V: 85237416 9828017e89e92b41ee082bb845663625 +V: 12111413 7b431d7d3418db13aa16e20da8a417fe +V: 45683712 ab0ecb700d02967f06c555fe36ecd42c +V: 56217834 cd4deb457556d9f90f4c8d19ab91f880 +V: --213--4 9e8ccce13ca29b67e0e2f7d4a5313444 +V: --13---4 b20a8c621150cb660bf724d9e655aaff +V: 15324-55 9e9268214073491f5471592b8d289cf2 +V: 37141856 2a0ca8001e353f88c40571d996f32bb3 +V: 53214867 c830ac007ae86215f5e3e421f8b05c8c +V: 25313534 9e0616daa64466947d9a786dc367e397 +V: 22221223 15ac14e7afb0425ca30b3594ec2e6d81 +V: 15312-48 9f7562106c9e7aca42589dceff287c0c +V: 14212845 880fcfe863faa4ce8dd22bb97387351f +V: 143-2--5 d8074543780301c50b56615e7007e6c9 +V: 26456731 bc5d66627de5e5fa2c3f22483fbd9ae0 +V: 26145837 9b4d5196c5861f43f84eb5a90d386732 +V: 3-21---- 6392474f4a775a992cb2506ed288ab40 +V: --12--3- 5cdf7d656bca6562e10328de20f0ed69 +V: 3-123--- b27d3f0640a314d33bc6ad346b98b0e4 +V: 1-3-2--4 411f9f37f6f24b3c7b673f3648aeaacf +V: 45673812 32a2ff36efed47b5f45498c8acbb21d2 +V: 24134815 df5cc376c3ecba2825adc9947f20757a +V: 14231-35 80209162b8c7b1ba2ad71877e8d51153 +V: 14122434 9ffe0ca817cb02a9eaabebe53e824b4f +V: 45431642 56bce963ba4cd5f28860358950998828 +V: 25153554 cc3185d62934d4bd50e6edd9754b37c3 +V: 23845716 fa1b8ba74186465d36c7e7434c5784f5 +V: 37261854 e2326f5bd39399b9d28eb7be7d1823a7 +V: ---12--- 2121b9021124043d7349c32e2f858d22 +V: 68215734 fa16b6b9af98e29380a86a6fedcf0f00 +V: 15221634 33abe04e778991d8de766861d39324d5 +V: 84158263 c71db3061b853baeb3f9b6ad5e4fb4d3 +V: 37215864 240a1d5f7fc9227051d43d7f642bfc32 +V: 23865714 cdde362f02085d20f8912b6dec5470ed +V: 25112-34 b49af1186dbb0b248b8212e54b570f89 +V: 47412857 aa1333bb4777ab1561aa462433af27a1 +V: 48344836 aaa073ff487e6fa9f2cde048a420d1ed +V: 33213534 080b246a2cb565fe85393ddf1830ad79 +V: 28612617 1a307458ea414869b8e6d2a056f7819b +V: 57314682 fcef91d6f8104aba23e9033fa5a2e7a9 +V: 24146827 14be0f2cf36f0f77f921234203fe51d9 +V: 35164827 85c37d57cee1b7f9e31fdc1e34024b99 +V: 2--1---- 59d43bd00cc40321e33f961d34403d73 +V: 26312745 ea5c635a481beb24051676e2d426935d +V: 36124758 09d4bb9d0ace04fd0a9e67dbdc980a81 +V: --12---- 555df6c79e30b20a85b258744015a460 +V: --1----- 664aaae907754c218eac78449302f107 +V: 25241857 d789918d625bdc8ae57a87f0705620ed +V: 23312867 d7d14ed77eabdba10e897fbaf2508e70 +V: 48125763 d6e4dcdfcfd41c24c1de5b894c9702d7 +V: 64125837 1abd54f13f8097d2a939e213e859600c +V: 27341856 dd25c2f04bd766b0427e3de9ba9a6e95 +V: 48213657 28de4db63bb2206140a978135ffe3d61 +V: 33232534 20487b8523ed033494835b1c09ebf88c +V: 1-234-56 f7c3b8576bc0f154a8f22b148bee01e6 +V: 46325871 893ec7e08d3f0a4b55b9e5af1c018571 +V: 88224416 142ef6f2613c2c7a4c9f65f51f274f22 +V: 76354812 e992b4e6302811b0dacc8289bb937da8 +V: 15126734 1af31da3d1e8c52683b8bbb1454d0602 +V: 77521876 61a4382c2834aeb1b8de6db72539a121 +V: 13122534 c337202ec1ddd673ad1d71a41fffe614 +V: --12---3 acf1a7f99572aeca10950b8f4ee88bc2 +V: 35213524 88e1f5b9709fa6f5c2080ad5da7f87df +V: 37124658 0172f801214d54119c7320e04cb59ad7 +V: -------1 b2d836dc52f53c12697de54978316b00 +V: 35212634 5fef8b71aa06f647c9a52a7ead8bbfce +V: ---1---- 319f2f745f8282276b6a2af328eeffda +V: 37421856 f9579c15d964b1a777aed3162173866f +V: -------1 c23ba38e315a59cfd72d02a8276989de +V: 28341756 039d5c78c555881a2db18baad15f6086 +V: --4213-- 74c86a247f9a38a56fb06204b4da00b1 +V: 35214867 a661b740a120439c99dab3592bd05dcc +V: 34312333 ef39e11380318138a04cde2f0b0577b8 +V: --123--- 13408a29aa3f41d950ced5fa5e89a307 +V: 15238478 7eb1177e865f30c3f9a72a4a9f722d03 +V: 47123658 7dd94fc57a410a4d1c874cbb185f4b4f +V: 48321675 0b7ba5275881a9304c9616f9d7174095 +V: 46382715 8ae29c87282779a1ff916ef08cfd85bd +V: 37214-56 dcfbc0e94ee6f3e1a32ede7176501b64 +V: 27135846 99c1e35449f51f788bfbf950d5261ceb +V: 24113635 a8ae3bffdd433278f62ac6ad06daa43f +V: 15223534 9a3e83ef7a2582aa568c341e68823554 +V: 55334518 e0b11ad010d1d899e1f4a2c0b78cb5a1 +V: 28415736 aab8a4cf861fd4849b53eb06e100b165 +V: ---1-3-2 23ce463064353d726caea161a0abc3e4 +V: 2-431--- 04acdb573b3b20aa0746a6b0b499bd89 +V: 67152834 45757642be8c7fe6b2c99dc3433af51c +V: 57214736 dd71fe62536a3125f7e1a8c33fedcd81 +V: 26315-47 fc636c8e3c7845d365814f3b69ea2fb1 +V: 45126837 bb79a341d25da6da950b6a7da21ee856 +V: 3-123-45 8e8b4a402102c9ce44033364e4b33322 +V: 36213745 9df7140f924e1aba6d7022604a60db5c +V: 2513474- e79947e2ef56bbc2e402589447c016f2 +V: 76275854 9bfc20fbf724f484d785f413124458d7 +V: 83712645 a7137d5e63595dc3abd876e227bd3d74 +V: 56124837 ee28b414256ed28d559c699519d0bdab +V: 16234758 dbc3b2ae38b6586b001983934b6a1dc8 +V: 2311232- 0b47129f951218caf8b1bf35f1fd0cc9 +V: 46123857 57d7997f3990f03a8d15d5192ca81830 +V: 35134726 d549175168c301e2c826ac5c00b45c3e +V: 57216834 9b8898579f751d6abfbc9b4b591b914f +V: 16324857 01299188960b4b3239a0cad2d4c42c5e +V: 25134867 174c2e9763e3269e5f62119e56671c30 +V: 17252846 286076c0ce373a2037e3d4fb50f3f2da +V: 57324816 e28c6ab23da73f62876fad302d2f5950 +V: 54312876 0bb9a47e90f076aa0242d26857cf733c +V: 54125736 a77e6c989eb7a4c4d5b19fedcef8705e +V: 22123856 8e7dd62b11322a6e742b2bbe8f2ef16d +V: 35213634 95e58cd7c48aea7e1a0cbab5c9ca608b +V: 36123745 d292ee2b1542abec738eaea6a7c35f51 +V: 2-21--43 6bc4bed870490203fe2def3874706a8c +V: 76312845 84c82862270ca5c33895c5a7edd0fba1 +V: 38158824 f9ded889c2208134f80b6f3ad150738f +V: --123--4 2fa85770cdc7a7e320f2fdc20b93e5b9 +V: 42444413 e3adcc29425fcbc749d7731ac264d691 +V: 2-31--45 fcdf69793004c8b7a09b6f0be12d53c4 +V: 14231634 30863ace9b616d8a4a697e9a029df9a4 +V: 57314826 8dd85a4c80afca93265d58cf1b37758d +V: 35176824 3dafd70470d24f4249c7da00b6301317 +V: 2222122- 62d875af374ae6b616b439767b517f79 +V: 3-22132- 931d0912646dae05376667f24d2d5b10 +V: 8812288- 1641b004e994796ee4667fa8b8b2c4a1 +V: ---1-32- e3e90e7d278a0399b0c2668dae4abeb3 +V: 14523--6 c613606c0e07b2900ad7d0532228e1a7 +V: 67341852 2e46d7dbdd4a0e31fecfd9d26d97a836 +V: 23211423 d5ea3a2298070dd799f20a00ce8febed +V: 23417865 d2d1a54dba89b27b56559a2ab3e2ba80 +V: 28314675 d13925332cc5d4ada191a5df1ea404db +V: 17243658 191ed8bf19c5cb6d6871d7782eced295 +V: 2-31---- f504e2dd66052cd618b2619f21afd3f6 +V: 26114735 aead9fe58962a052073c3eba978ce3e0 +V: 27134865 d4a556f9a1d5712704424e39c69bd139 +V: 3--1-2-- 47c90820c612b4b9288405afa3ec8dfa diff --git a/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2007_tally.txt b/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2007_tally.txt new file mode 100644 index 0000000..afa847a --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2007_tally.txt @@ -0,0 +1,507 @@ + + Tally Sheet for the votes cast. + + The format is: + "V: vote MD5SUM" + You may locate the MD5SUM corresponding to a debian login ID, + given the associated secret token returned with the ack, + by simply running md5sum. Forexample, for login ID srivasta, and + the secret token 0123456789ABCDE, the following invocation works: + % echo "srivasta 0123456789ABCDE" | md5sum + The vote block represents the ranking given to each of the + candidates by the voter. + -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + Option 1---------->: Wouter Verhelst + / Option 2--------->: Aigars Mahinovs + |/ Option 3-------->: Gustavo Franco + ||/ Option 4------->: Sam Hocevar + |||/ Option 5------>: Steve McIntyre + ||||/ Option 6----->: Raphal Hertzog + |||||/ Option 7---->: Anthony Towns + ||||||/ Option 8--->: Simon Richter + |||||||/ Option 9-->: None Of The Above + ||||||||/ +V: 111111312 5de2d9bdec162e7ad7d475f7a3a912e0 +V: 296153487 82779753e0665892a0168183b4d03db6 +V: 222142443 8fc3f20b27697bbc84c5d14782ce29cb +V: ---321--- 85a74c0916cb5df9b7c33fa49230ce73 +V: ----21--- ec0aeebf04b664751df5a3113cdba64b +V: ---31-245 3bc131481a5f0ac8d07748b93e275f0c +V: 344412645 4e58d78db380234727c3170416c9720d +V: 392156478 737552a1b770c84bf44b12990d0344fc +V: 72135648- 5b6ecb7d1eff7fd31097659d49f7f9e7 +V: 263754189 0b946ed4c6fb3f3751e0886822eb7871 +V: 16421553- c6fa84467050016ffaf55542b74fbb8b +V: -----21-3 c93cc619a255967030ffab7df2ef66d3 +V: 483125576 9897dfb2d83c8aa480ece204e81e2254 +V: 355196924 00115bf3d3b02e3449305ebb7dfe375c +V: 885214893 5cca377bbf7b08de0245764a31300820 +V: --------1 f71e69ff5942550e11505ef6896a0612 +V: 163724865 5e3bd536aaec6eb14614b2a33d55b855 +V: 372451689 a995aeb8bcb4b412ec7e795dfad9164b +V: 5--2413-6 2931ffdcf2d0e5e76131053141afbb28 +V: ---1--2-- 16fb70036d9ef9054ee5e18490e65ffa +V: 34641425- fcd1442e37dcd5b93e80630452beba06 +V: 183624579 570a5ee57b184758b7101bef1376512c +V: ---1----- 58a680be2eba20a5c56e0136c755e46c +V: 185324769 8b4c2ac7df2dc0520d2dfc324f0938f4 +V: 566213254 9faa59f85f4ef321bd3b7b1eb95a962c +V: ----231-- 6fd0574788f5e39d2f3ef5f419ff45d7 +V: 287134695 3e4019943e1c2a06086b8fa9b7f26b13 +V: 165324978 7f12cd29047b33dbbd39f1baf299b2d6 +V: 714365289 b5164cdf1389b949b2e9c9bcef97bfe0 +V: 1---2-3-- 883b71a74f1dafdbabe41b7e9bd282dc +V: 562155643 6cede7d373127b3f776572492f2013df +V: 492163785 c57f0d386ac109a968890e96494c7d19 +V: 581342766 e2f6b90c93ada18c05c4577f432b8249 +V: 991999992 c8e99a4c0bce7b29dd319d798d917808 +V: 2---5413- 9f8220bf2318e743aedba861ea728594 +V: 296331478 b75d57ab25da0dd20ba763e25a1e2f8e +V: 471325376 38dd111235919341e256ca0beae75233 +V: 352188839 2e12a634b4335369041e17192818ea4f +V: 6241-5-37 3951ed0cce057646f9cd78208b87c5f4 +V: 543815297 c8875690b94b5698d52a48e068928d98 +V: 492361587 e205db0ec3a7fc454c6f4050e47aa992 +V: --21----- 6dbff1a985e92da42f3fcec7a19307a0 +V: 199299993 23101224c5d0d377cef8f5e4c2e45045 +V: 187324695 1eaaa132079955ea5589485e843646eb +V: 583171964 a2a6d73a291f23663332156edc849cf5 +V: 491365782 88ea4569c43c1736b4c505603916a859 +V: 144332543 90d3b334e399882da35d69bc72726027 +V: --------1 cf3b91dea6391a8a3331d13eddd10b59 +V: 465155623 e0dd82a30b72c1fa483215469f1b6972 +V: 274138956 d7dab11af00b8028015098df5a1d3cb6 +V: ------1-- 43b414518d6603d5345ec4bfd08c7a53 +V: 48561327- b4b6417a49c0b1f38aa73adf0fde98fb +V: ----2-1-- 16dafaa67afe1f28ad17f007e1eafb85 +V: ----1-2-3 864d806937c7b6ee21e381f6e8b6d699 +V: 25341355- abab795f67d507315e39b28884ee1247 +V: ------1-2 1435df948819103e326dd285a870d7be +V: 4-212-4-5 9a71f50326a4cc423e78b705efba39bc +V: 195264873 3d0d30383706da28452caee427ad520f +V: 377815264 6595f5ab403c46ae3e5ebee012b5b439 +V: ---1-1--2 3d0e67d584384018f0dd6960a604665e +V: 537412176 45f67677e48ce5733169537737c8e461 +V: ----321-4 62866e67f4dce3a01f3d970befc95175 +V: 45551325- b73343c1560d714e065e72a8565036f3 +V: 278514396 97a23e6b79f7ee634d2521fbb1808ace +V: 78261524- 402b4bd1a358667dd1e695f510948f0a +V: 476123589 9008efe86a4ccaf437bf00c126f2a81c +V: 5-61423-7 072cc65cd6b7e037301594be54a56afa +V: 443122635 f8c7983399cfc9acb1e38054d970f9ed +V: 372144576 3458172b1523f8ea96785bc156581a31 +V: 191122938 2e78f4e0c2f641934c56fc7f8910b066 +V: 2----13-3 7c747a75c8a8afe77d20f0949b3aaa83 +V: ---1----2 f5fcaf54dce30ace7adc8688d2ba8aab +V: 34441214- 8b433ccb9ae7e3b131f703d1a271edeb +V: 56412378- 875dbffdbe89c5dbd8e832e6b892712f +V: 4-31-2--- 1def3be27460b7c59799672e535beef5 +V: 372165498 59b6c624b34091e14cc3ca2c490b3719 +V: 582168997 fb92a33b945d36f3f8051c0049a96646 +V: 377914265 3a0053ac583871542503eb0e8dba7fee +V: 387415296 0bbf5448890cfcf57a0fed19efa324cd +V: 111111111 e7db684ce9919802b57a0a3b55468098 +V: 586432197 f39d939d8095f8e539388aa988c0713c +V: 571242776 44d42fde51290d065e8efeb787eeacc7 +V: 233331134 a42d1851a9bbd882d354c2caabef93df +V: 496512378 035fd8c2be571cf93a1632554513f8cc +V: 895132476 030e90534cb75b16ea78229227fb81c8 +V: 487921365 b2bbee806824f770133b7aa3af04bf27 +V: ----2-1-- 8d2aaa61f2b975ca1de55236c2c9a36c +V: 862183968 c8ff3b650fcd6dc86d5337961f30d55c +V: 456728931 e99a33058dcf1248134f26daf9d9167c +V: 642187935 6feefc11d1ba5bbd386a5cbe2cc54c7e +V: 26713548- 4c8e050da3f0d40830aa649c7e7eef41 +V: 6-51432-7 f45968e0d9b48d70e9f19016d2e6a3bd +V: 465932178 94d47901ffa8a4cbc53abdbb7440bf77 +V: 487532169 a143468aa15a1fac903dad4bc7af7356 +V: 256143978 8388d60c8a8500ae717f3d47417a985e +V: 197236854 50bd4fd15d40600ffe7dd0812ec7d487 +V: 295613487 d60ebd8b7797248e8004fee92e1dc707 +V: ------1-2 f31d5f580cfa6b93b359d4250dfcccbb +V: ----1-2-- 9e56fde5c2185837f240f02771659a7b +V: 2--5431-6 1fc8967dfc0c77a494b799309ab589af +V: 547231968 9368b647e156b39acfb14c4862fc8ddf +V: 111111212 7a4c4f1b8f580c452f80b0e593376072 +V: 4--3152-6 26940b24a7153e4da66d8a52f8c66220 +V: 987165432 84b093a44ffc6d107ad7b469e127fe8f +V: 683142579 70aebffad84baefadee4ec11a6b10ee9 +V: 173244665 940ea2aff5130d4c31a871681bc6eb80 +V: 486313547 e283a692d7c5e21f3e5a06620e761753 +V: 6-53214-7 ed9c42f95476d5bd9aa25c322dfa2abb +V: ------1-2 3db6fc96af0bed387e7767d885d15129 +V: 189732654 401e66df6743eef21ac0a9521d03ba1e +V: 583924176 6a2f93593aa6209a0d2ed1033c8d0cea +V: ------1-- 30cc4fa4575d8924b52b7bd22938ccd5 +V: 355523134 8e290f67b1359e2b42030d0f4b3b14fe +V: 662135547 b81159450b1dbf5e247f6699978a3771 +V: 3-21-3--- e35a7bcac43b3c7f49865a65d170efe0 +V: -----1--2 f2d740fc3d33021470ad78feb1787295 +V: 487163259 a8f3dbec826cecf4654d28cb7e006750 +V: 574362189 36c1c8a5adacaee09ec26e74e5098de3 +V: 18652437- 84a54ab0641b9672ed1e1c01ee747e3e +V: 582168997 dce81c3b9ecfbf1b1577c3f3ebf7637e +V: 377231465 d3e512c9b0b9475517276aa35e19b57f +V: 3---142-- e04c48b370b8ec4f3d2a6fe55104d45d +V: ------21- 7deb96af26706c3e7faaecd287f6f9a7 +V: 344134245 067301f57174bdc1a190047ca71d8d28 +V: 1---3-245 b93c1808abc7145a2fb50fd805702b98 +V: -----1--- 4f2512ecc8efddc9978f9f3b4645097b +V: 385126479 1de2591052e74e0f671172e880a358ab +V: 567312489 0e4617eea72f2a15fb4e98e84b1000d0 +V: 124873139 c72d232fd42efce75e8b49c00458659a +V: --12111-- 4889813b13967043023bfd00795b41f2 +V: 444144243 cd594cd6d4b3a85bdd1dedfdd3149231 +V: 762584139 ec51b658d6c78a199790d601c5e4cfa4 +V: 598123476 67cf31ba28bb7dceb57ee8f68b4a311a +V: ---1-32-- 7e36458326b532e5fbdace7230536504 +V: 876543129 fd041dbe5623c65f56351d3d4b929456 +V: 586413297 4d58d5cf2f37d1440b2a07fab7151f54 +V: --------1 b09ed6026229caa68d66e730fb8a4a8b +V: 2-22-3--- 02d3929e6d994cb541659531e10dd7aa +V: 266513164 4c5ab909158a181c387fa45034e609ee +V: 23332214- a71096e3aada5291d8c20019760ef560 +V: 651243897 a01ea206af27afcbe5264456be6348c7 +V: ---1----2 ce7e62d5da29d0823d96ca6e026d3dc4 +V: 1---32--- 35fea1c47efd4f97d02b482a001aa6be +V: 346815279 9b1fc26351bca7560c19d6e3990d5f2a +V: 576312477 144afb1c547f39986811fd947dec72a2 +V: 592134687 6098c043773d4fb0710e15c0a8b71231 +V: 574216389 a782449c23cf84a322be471a9c452cdf +V: 287146953 56506a20b0acefd21d9a6e354fdc36fd +V: 385621497 458b63db0a1dc0ec93adf00be7abc8f2 +V: --3331139 c46e4526db436392ed366f88a5d9d496 +V: 265431978 f871c7777a9e4012481323c57d89bcb7 +V: ---1----2 080d2758523efa3ccebbe873f6ef197c +V: 2--41-3-5 71496d6e09335081ac4e49a8c506e40c +V: 3-412-3-- d943b69cf304d61feda9e36c8c4b3c00 +V: --35412-6 fdb2b8c51eb9739f9eca1138eab195f0 +V: 45214354- 4df1e71f298c322bc241fc3c94996b64 +V: 244361745 2e6c1b374f54014bdb31a41e5b9919dd +V: 554134255 10cdc8478283ac2642a3fd78d353935e +V: 583162974 b87266251cfc78a497895e4dc71208dd +V: 596231487 ff06deda5f6815ea89c5b43de59af03c +V: 111111112 2aacea3dacd4d5f4d05571faa1138a12 +V: 154116732 8b0caf9a0a525c75f012189dff836d36 +V: 5--4231-6 b75cde399f6e1d6be9706d6c16ae8f37 +V: 222222221 ae449dfeff4775355b9ca663e30a4fab +V: 47352618- 2f58a9c394d01d254755ec3bc9acc29a +V: ---1-2--3 e9a2552055e05d8261082c09d2d10e86 +V: 4--12---3 59b3cc4f7d1ef6c906101ca6bfdc6e77 +V: 296143798 de294802bfd760bcdfde37d1949ab495 +V: 287643159 7f1b781af70c0a2565851ad537c3aa59 +V: 465213357 e87f371d4979e1e96d10f4410516c9a2 +V: --------1 1d596053c880a9e08bd0e4e6375244ea +V: 535551255 3ab4be2a34d570562d6101145e7829bd +V: 3--132--4 0582f1ee87127997455fb714808a4a32 +V: 267418953 a4fbd4c9df212fcc42a6a556c1095454 +V: 554125356 480de81efc20be2b13db3df1c10c28cc +V: ---1-2--3 c431f05de380f86bffa3eff6dbf55910 +V: 3---12--- 76c519562f2b6ece724663f9057bfec6 +V: 774325176 eabd71100caacd9886ac112755b9c80a +V: 564323157 8677b137910cd002ae49b912effc89c6 +V: 482153769 c15eaa53b3868422a7491e4cee1e468b +V: 385924176 3eb456075d86629034f72794d01d77ec +V: 333332134 63bcbbf01456a69ed0dc1148a8fd7999 +V: ---12---3 f269f96fc40edf20bee614999098e2db +V: 663324165 07280de9e2136d41e3abba5df1eafc2d +V: ----1-324 5a3aedc715ab8b5a71d0af8d0f3cee7a +V: 153312545 f7c8d103b7493a94de336d5bfedfdea9 +V: 1--42-3-5 a4056d79e458ba9b240937109c5c961f +V: 586342179 88545fcc0c659eac3e994d9c80be08ab +V: 888284889 52bbdf64b4d6045954fdc56688ef66fa +V: 365271864 747a037ddc920c6d131ae249173ed0d0 +V: 999991999 dd7c59a028b7d3dd0bcfa56701d40bf6 +V: 753196962 f2d58336c99b6b56b96fe1bd70fcb506 +V: 197235864 f1a36539afe221432a673fddfacd84e9 +V: 566623164 be5c2112e5bd09fe9771dd7cba3c80c5 +V: ----231-- f3de32932dfe8c46bf9af8368ddea379 +V: 562178943 018c4083746f12acae080ac80441d5ba +V: 498321576 b1b6ccdfc44a7489a0f530c19f83c935 +V: 4---312-- 210d724ac9d0af7aeb53d86a93bc501f +V: 246381957 493f32eb0149a3778cc9248bd7b1b623 +V: 293949157 53e63d6e4958e51e99d0bcb5f566fb6c +V: 576132489 eb34f24957923357cc0e4e1fa2e6a840 +V: 694521897 4561d375465b5cc82ba5b7da0647ca8a +V: 3---1-2-- 00ffe84df2b1202dfdc005900fc11f5c +V: 382144789 02ee19e06db684d192fd1e833b21287e +V: ----1--23 98f1438393ae5a3e6dccd71a34f3f88e +V: 15433245- bdfb1dc25bcf3d9b8bf8edf095fb59ef +V: 225222134 cb6919843f9fb2e1bafefb6a413afd8a +V: 111111111 8844e5b2800219ada59963f2b54fdab5 +V: ---1432-5 86d8197150520a659727c56b269c6b2c +V: 4314-4-42 6014b669eaaf068938c4bd76fc0c6503 +V: 444442413 3984cf2163162502529cb13d78956780 +V: 396257841 98177070aac46939e188e030e3d62496 +V: 4---123-5 6d467c6f4cedf79ce0c4e9d8f4408ed8 +V: 382174965 49012c3bdd90f1745b8d16df135a5571 +V: ---1----2 0c08ea573d54ea48a8763efaa6d17337 +V: 361342457 296887cb5532784f210a1c7caa48db27 +V: 333133932 dab3b11f08a9e01169d6cdfd1cec9e47 +V: -----123- f58b879a15f04ff62a942cb69d0fad16 +V: 673524189 39126fb06601fdfc55c10d7470a0e764 +V: 596142387 32a1fcb031835d8de9eba04ef66c8b22 +V: 385214376 6223e697bbeaa08d50cc4937e16196a9 +V: 187425369 a85b88c2163821431424202c3509bb74 +V: 244443145 513d840e74cc85a997055c4118844cbc +V: ------1-2 acda34550cf3809cb8752ce647109d78 +V: ---21-3-4 2e74a63c4d6b9e1a4eca6c8da108097f +V: 2-22-1--- fc31b6fe22604434cf3e425fc3ffe475 +V: 664193952 02d0f4e29ce8b6c12c39098673eb50eb +V: 3-21----- 83e3167965de09ca3cded63c7dcb8e8c +V: ----1---- 5321943134fdaac12c2571361e3209c2 +V: 273165846 5ea27f991311bef0452ff35feeae5f1a +V: 35214343- 3a7bf3609002ea58d4357ebc5a17a691 +V: 514523637 a2c5fa69b9bc838371d7f3bb230bdde7 +V: 265413457 70f7918a20744aae8c73a3c7d09efeaa +V: 3---12--- 93596e6dd5046fec39b84360d73237b2 +V: --21-3--4 22dfa2efcfe4ce437a5896b2521424b5 +V: 255322154 c3079709aa5e629e38f289c9179eb487 +V: 355524155 852c672036e2c380fd3b72c1fcd3820a +V: 522184866 da7b8095312ae94fa3c3ec1c58f230af +V: 26-154-37 dcdd3b22679d894baf2dfdf578abeca6 +V: 565215364 aefc7247dc28f1e1f741258306679c4d +V: 295314768 ae3901f16520b90f7aa52ba483dd40ca +V: 2---13--- 8047f51cee67e205bbacaa777348e469 +V: --------1 372bd24e089a9d7726756e056802735b +V: 473132356 8ae4163bfa6a1b0bf41ce4c37b911506 +V: 5--3214-6 763173424abf54ffb1736849a11ff871 +V: 343132645 258a5db24ad7f2ecaec5e42a9d4705d7 +V: 164235789 51ee8107224ef5a0621e57733cc0d8d5 +V: 675142398 e4cf8161965f37b27645edc0c2ad4af2 +V: 498132567 94ea66e5524ad103fd2a4e825e80e5be +V: 275513476 d90bacaf87ee54e879d6916b9007638f +V: 344525154 3dc49a068780f6485d0af621b5b8e911 +V: 643271965 31cf29e73d41b93fdf933a091a5e3b22 +V: 333313234 4d398272cc170b83d1d4bb38e046b231 +V: 341241145 66a5fd284dd053f97f856ee85c064206 +V: 4---231-5 f6d23c2a3aa9a7b241bb6a4f1b9cd157 +V: 169854327 f5032d096a80595a75c731f0db8f9ac5 +V: 152142554 451d02c778b4afcdc4ce933e6b6e4cdb +V: 574412376 2f3ebbed8bc9ded4a725d93ce1c4973c +V: 682145983 8be65a7c019f5cc303ade7d1d3f607d4 +V: 2---1-1-3 d644146add46155ca62cb86ee7b1626d +V: 244155643 1dd2140c3af1fb9bbc08a46cfdbcad0d +V: 255123456 84a85cc820ae48b691b69f9ff507b878 +V: 265311546 1ec1989a8abac2bca278edad96c8f0ab +V: 394122885 d83c3ddd0ab56881474bae3be7adc135 +V: 3--142--5 f49c6238d3620d159421dc3c0c51e5ea +V: 355125456 742bc19e1efd4ea4529403ac053fe1ac +V: 586134792 a1d8322a946514463c0a47f2391e59ca +V: --------1 e3f5d8bdb0547e74aedb0e067def31e2 +V: 654123879 7b04ca30a2af51a6389ba498a7efbe7b +V: 295615384 ee8968ac72017b229193e8f2e775916a +V: 197234586 e571ea4a611d3982521507b44422f9cb +V: 2----1--3 bd4b6e615fa5c8154f0a7c92298d5e37 +V: 233251554 a656f68b43f6a033893898d9e59f5591 +V: 2-----1-- 27eba01b3562e3b6276fbeb2d77775cd +V: 286621269 f03249c241b60383117b06fcdd94cb13 +V: 752186943 667913ce66544f3c55ea195672a54ea3 +V: 474144826 3f87280c40b2088ab06d8a46be823698 +V: 376542189 bf2ce69341e0fe56af28e6c601e151d4 +V: 492142687 c85dec9e5ccb61ed8e6676fc02478fdb +V: 2---3-1-- 960d0f72870bafad41b2519819370285 +V: 355162647 56327ec0402144a7af744a7da21be341 +V: 276134675 e19b33e179295b1cde6f2c888ffe4720 +V: 465423157 01f1ba9478f9c851737cabdedb2918b7 +V: 555213554 06c43c5f70b81389c0f940c0e83811ce +V: 58632417- 75992f292891b0aafc6e2f5c6a0ba054 +V: 111111-12 f1983b48853dcba93afda7f745e9d724 +V: --------1 aa9ddaef3c62aec7e66552179a27f613 +V: 355113354 5ce16651123c955d0c75ea4416759586 +V: --31-2--4 e677ea5d9d11e73d5740d9dd98e80cc0 +V: 2--221--9 11704a4fd006ce5fe2dd8c7219b6b1ee +V: 4---2-1-3 2018f2fb975f57cbcad50930f27d9f92 +V: 375914286 8bc0c1a4347c03ade54ef74480c7f0b8 +V: 4---32--1 91c3356f3a8be093e9a439206742130b +V: --------1 44300321087cf840cdba98efa91e0d5c +V: 974523186 911309d8774584421cb2207b6688f6c8 +V: 2--543167 534dc18fa8968c8d6afbe6e7bb1bb4ab +V: 162376948 27b493deda85a020b32d87f2a866140c +V: ----1-2-3 eb59e40399d66c00c246325bdfb6dfea +V: 133378924 7fcc00f3a13c0ad091fba88aaba22878 +V: 483127956 097a5794e1fb240f5526780034d80156 +V: 436125276 b5308be450ff392d904ae96e23074a08 +V: 578314296 a1b4a901e1c09daaaf14499fe2ee0660 +V: 4-3326--- 750c674329b6075fb8274dfdbcf4d9c5 +V: ------1-- 448873ddcd452b462cf4c5dd0ffb82b6 +V: 398726154 3948e05c633c5791fcd756d1ccd76392 +V: 355321254 1a13f53484c844bda94aa55636aff06b +V: 166324753 747164c51703185ac5ec34ebc70db2df +V: ---34215- 97872544e77cc4c586d827660254e426 +V: 366612465 04a7bf5b89fb416de7a3a1e87fe0583b +V: 4---213-5 0396ea3ecdbe8ddc983c9b8ade292b32 +V: ----231-- 8931ea4448a7a7c42160a2fbd3b2a2c0 +V: 175372476 b0aa48e827229ff7b892d83101b40a4a +V: 46512738- 3ba3a82e8832bf7b8a0026227927807b +V: 189723645 7c85d2372017f0ee0fb704ed172eb683 +V: --------1 156186b54bff4c7907b5df6464d8feed +V: 526943991 b3231587079f02856a593300acadbbf5 +V: 888819283 7fb70747a0d5320877d28d97b770920b +V: 3---1-2-4 e26de25d1a566a20401ae8057f545102 +V: 496512378 764f8e7a378d3f6c76ac132e57108cde +V: 264123335 fd2b183c89c1fd2f05d3359d678caf89 +V: 456782193 7234ee67b6ca1902ab2e00ef7d720df2 +V: 355134256 1c0e8c39dbb2b38a75a4c3f19ac9259f +V: 354513267 aca0c504d4bd9914379976fe1d18b275 +V: 476532168 e0764d62ddad54b8b6e98fb089a17fe3 +V: 466412375 9769a406a06a0235a504476dd27df65b +V: 1-------2 e24806bcab753eb0ba0a3645ceab0539 +V: 111111111 44be34c4339cbd9e9518dd50174ae944 +V: 374281956 4500e4f45a2f577bd8143c772d67f5c1 +V: 365187924 a25a9bb597997a98ae0f32aeb41955ee +V: 194372856 cad4db5728398cf2f58caa33c919cb3e +V: 596416287 4cbc67bce81623d5f6dbf5b689855ecb +V: 381254976 e3799870d31e15c5fc2602f337564757 +V: --31-2--4 05e3025d083f1c723df5f62ff1a9f2d6 +V: 3--321--- 75c92f1fe4b33daf63318c3443d6eeb8 +V: 785342169 8f5b77fe9bb0791771c04c3bec9cf71c +V: 23232114- 608fd99471a69c86da4fd9b2d65e4041 +V: 121133524 3792a22312edbe0119f58f0429208ea0 +V: 195324768 fd7444f48ce4680da423975408624084 +V: 254731689 7bb119c375699411e19265067c0d488d +V: 374152698 65f8f21944b9a1cf3b5aafacb1595a19 +V: 4---251-3 fcd68ade4a0848e838425841f1f6e173 +V: 456789123 5ae533c50c1040e84e4e4c67889e6cf4 +V: --------1 d8d3bf624b18531382e159cd2beecef5 +V: 896115792 6c811386473a4cd9689934c381a2e223 +V: --------1 702f6278c262559addeab19aa451d47d +V: --421-3-5 163467838b8b0a764368392812d20cf0 +V: 589137642 872bb1d79a6c6efbfedd09fa4144b98d +V: ------1-2 838574ea391956187d09cef22818cca3 +V: -----21-- ce617eff7e6139505741b9395fafe735 +V: 273156984 5a48e39939f7eccc20f36e873cece388 +V: 199931498 4127f49f613bd079a5c61bfaf27f9fb8 +V: 952184763 18669f7a2e8fd42eb615471c7a24988d +V: 444211243 8ea420445c49abc20903ad72e0115f01 +V: 156723984 cea35d528fb437c707a4f3da25e8ee04 +V: 394152678 cb1b0ff1dc395c3629f4d27db93c35be +V: 495124478 958f9f64e3fec695230fa900dd9d58f9 +V: 76-1-438- eca2dce469616c429cb8ecb5050f3940 +V: 4---231-5 731c3fbc78d38d309c49ee6012bcea39 +V: 3---122-- 5ca9803e8d3b2752f96c95cb2f3f9af1 +V: 475143286 58f0a8ba4dd1b1606b58057f565a30fd +V: 4-5-231-6 5f38bdb077a31e32187baa761e7cc636 +V: 562175834 a466b5b91e43b9a61af656da6d530627 +V: --21----3 8b3755dc6ef27e43a346c9a9403e27ed +V: 1-------2 30c4333004e45f08bb7b8d84f4de638b +V: 2--33-2-1 62f3466069f39f60b3910e8d32edc347 +V: 2-----1-- 013cae7d31d7a577bbca3c9e2bb498d8 +V: 399142498 6daae1c6289aedde583e1e967615e82c +V: 495932199 91df45e7019d8976f0dd31f2c8dbcce8 +V: -22131--4 a505bedf414f1415bd751b6ae28804b5 +V: 274414379 96fcdcb871e4abe1af7547e0c231331c +V: 798314265 ea0bce5bb1aff2deeab387c4e4ed9c3a +V: 597321468 3ad68bd9961a5c5744f52647e2f2504e +V: 255414553 917ce5f81938ef83a4f3db51ae6178bd +V: 174293958 976ffc2fe7dc2d4cad3a8f07f68339c4 +V: 366324165 bdde81ba338ddcb33001f359c5214c90 +V: 555314259 95f8d0bd38affe6ae9c3d71d2199ac42 +V: 478613295 50b490bb14d6fed354dbab130f07da2e +V: 14537682- eac404717952d119d722f2f15fd3cd00 +V: 239814567 2d5ea2b98b818352b34e4344a91f1fc1 +V: 333331332 5c1a7e3719bf2ce986c1d39d18548f34 +V: --------1 bd66e05d62ac660c5c74ee274107628e +V: 2---51436 6b78eb57b435fbf29633bf1abe9a27c3 +V: 392164587 24d7edbaee90b8e5fb8d800ed2ddf351 +V: 173642589 868b4a0cf7ac9e47f64659670b7c247d +V: 37514268- 8c40da82b9b3ab4983d5326de87c781b +V: 345617289 dfe43345c1dc20f29c8750eb06067da1 +V: 285912457 82b5dfb5c8756f3902cb2553309ac0d3 +V: 222222221 c5b28d9d485fb3841d31e82438e42ac2 +V: 477215376 175d997dcbc410c4386c00e826b51273 +V: 342164645 d91be484ccc0cbaf2ebbb274b14f71e7 +V: ---1-2--3 e5f837e3b0a4408b9a201a0014a43f07 +V: 999299199 452ee865caf3d481994da4d869701f76 +V: 1--2233-4 ae95067b3585cd16a60ac24685b2a0c4 +V: 2---1-3-- e98d8eded90d2fec8bb9c3eaa5de2fcb +V: 794316285 a3e5a1018c069d7e9c86668465ce0a32 +V: 498726153 ae9aad17831770e8909ee41908a8f2dd +V: ---31-2-4 8573c551f716d78cad467d17a1df1121 +V: 233241645 1352574fa909e3b2eb1667a250596344 +V: 46226---8 4bac6c66bd4a909a4ba0bf446640aaa0 +V: 163284957 847d6aeab5fbb1a3849b7e44e6dea116 +V: 1-------2 04d3d08aae2a5406a086129a5a8281b3 +V: 23331213- a00acc5da9cdae31356f3e4446d83487 +V: 371243856 bec3edbf12bb2f0cff96073fb638b078 +V: 195726348 073499ac9d86f36aa314e3a0fdd6d41b +V: 3-21-2--- 25cd17899934e7aec5c36d412b143fa1 +V: 367524189 36432746596bd9078c342e86b4803786 +V: 387425169 8b8c6575aedff7a0ce8363ad23de6965 +V: ----21234 c46c4edbb67cc243051f483fad0b27d9 +V: 675132466 9639e14905858627e4069e1362be836d +V: 1---7-8-- 2dd0cd8630c080193e998b85a9c6b343 +V: 583153986 e722c36ade585eeb09e41475fd6b2645 +V: 3-15-24-- 9598237dcc4e6c2d69ae1386004cf2ab +V: 567432189 5a550769adc0cd30c8c143b008536098 +V: ---31-2-- df42a1a9e276a8f937aaa517f9989a63 +V: 3--5241-6 2cc98e3ff1202694eaf23af0203dcbe7 +V: 454513256 aa11975d8353c88727b59a6a65eec05e +V: 472135869 973b9384b33e21e06b79676b6132982e +V: 876542913 0b768ad1c7eba0d6af8ce4f9013d3067 +V: 583421967 a3ebcb8830c373594a6949bbab3ef605 +V: 473562189 fcdb1e90e4e87f0b313fa3c4ed30daa1 +V: 492135687 8270767ec9af8ff30163c1ba11219d57 +V: 473152986 c863b36f1e9bb6a2e0689b07c7cec405 +V: 34321215- 2048937296216731b4942f13234732c7 +V: 234615337 c722315d845bf3b8900873f01a1b6d80 +V: 894125376 1c74a47b123bf2fff4df65a6a82ba0d7 +V: ------1-- b513e43d27e8ef8fbab1397c1ef0177c +V: 378214596 2e60fd12c3c8b523560d212c0f8a520e +V: 466236165 ed7047447d8c83123d10ce83371b048f +V: ---3-12-4 06a00e033e301fed22a75c8eed28c2b6 +V: 465213265 e57783d1b8b193db6d2b0cca96f0907d +V: 663314165 c9e848ed71b49de37adb3729c610981f +V: 494613278 104644f9f0daa43db0ecba9a425b7552 +V: 184536924 7d05d0a4355829223b4bcaaf18e0292c +V: 695421378 aa785047e901d85ceaddb8d39ac8fbb9 +V: 196724853 673220b63776cf4cdba842d83802a29f +V: 2--11-1-3 cfca0279d8f7a4c7a75288e05c3d7c23 +V: ------1-2 e26b476320b331b4f47d01807dce2e1f +V: 173244536 9091a67166331b5b8a0af81dcaebc1c7 +V: 441244443 a25d9aef343ebfcc2fd8eff632042e68 +V: 3---142-- 26e7e3c411611e9dfa6cb5e12041091a +V: 388824189 2be1f033b4144e56b1e29b207a014de3 +V: 583246197 a127843a35f49ec7900797879d5582aa +V: 883312497 ef7cbe1df6a2e3f9c18c0236739a4469 +V: 164612435 c0141e6786e1d7fa0ec6f14ab42af5db +V: 587321496 9880d041dcc6b49ad32ae1b87d20874e +V: 483257169 5a5536130285fa1f49e59b12f284bfb4 +V: 47216358- 36cddfe1a10d5d4ec18789f68652cce0 +V: 781243569 e81b6f4d2d685a1f0f7199fc6602e78f +V: 682143597 b47401bedd3e9c14d3083857473a2e0a +V: 2---413-5 38552e43c001aac5248885ded1cdb116 +V: 575121673 d85a7e66322100d2efcbc8ca51ce86f1 +V: 273451896 a3875fe4e1af9d07cf9efc661954e838 +V: 193991499 646a655c6c6b8ff073cb74cb8abdfdf0 +V: ----1---- 434d6c54c6ee22d9838c17dcde1e8714 +V: 111111111 db0ba98892501139afd8e3590c2388ef +V: 14112234- c96fd6283ac8460dde8b4ef281ec6f03 +V: 173242856 af7339fc519870f144e700dfd1366b98 +V: 3-11-29-- 9054f0443ce90b42d1d5d8915e6670a7 +V: ---1----2 fdbd02faf74cf40038c7f81887c5b1e1 +V: 372135574 ecd69fce4975cfe9f7291bbeb992bd24 +V: 471326589 6dc06d9ae7f4482b0f54b52fe8b71bf4 +V: 79518789- 307439b033a02f983be37e6b0dec868e +V: 273155467 196cc4e7fca7d16acb693b92d6513e40 +V: 613254978 73d1b0b63a1d17919b20b8c0bea1f0a6 +V: 265134466 cb63ad319b5aa1a7c7aed178ddda311b +V: 372156574 21d94a46a5a9162b906f4c4f985131c2 +V: 696174-97 f077e7bd35a3194915b3002853a9e374 +V: --41-32-- c93781659b330e3322699299d7b0263f +V: 4-----1-5 92a18fedc75e10f238b593a1068cdb2e +V: 582167943 9fb47f93ec4ad670378899f259e1cd2c +V: 185274936 0dd48de8586a03bc634d5b7f88f2ad09 +V: 794523186 6461f50716c1241c1e94c44d01b9d223 +V: 444241443 61b0198899c64e3f3893662524afbd13 +V: 2---1---- bfa429a7aa1dd33851efb4deb1a182db +V: 166146965 8be478fdc7fc4f6c5f5ec767c26b7070 +V: 361289754 9ebf1d644affd657f6c8aacb7893ad70 +V: 222222123 a6ebe2e8cad2d93c0034ea8a1f800a36 +V: 45123245- 6524617fc241314119683ac3aa923033 +V: 442134365 8f663a21f0cbff38bd59272657674974 +V: 455512359 56947a39e87ec487c5587de1974d2266 +V: 23331414- 774b5d5acbfd0c28486987b6e2858ff2 +V: 672185943 7ad3a7548af383e4c6e556f064821e87 diff --git a/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2020_tally.txt b/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2020_tally.txt new file mode 100644 index 0000000..be384ea --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/DebianData/leader2020_tally.txt @@ -0,0 +1,359 @@ + + Tally Sheet for the votes cast. + + The format is: + "V: vote HMAC_SHA256_HEX" + You may locate the HMAC_SHA256_HEX corresponding to a debian login ID, + given the associated secret token returned with the ack, + by running the sha256 hmac function. For example, for login ID srivasta, and + the secret token 0123456789ABCDE, the following invocation works: + % echo -n "srivasta" | openssl dgst -hmac 0123456789ABCDE -sha256 + The vote block represents the ranking given to each of the + candidates by the voter. + -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + Option 1----->: Jonathan Carter + / Option 2---->: Sruthi Chandran + |/ Option 3--->: Brian Gupta + ||/ Option 4-->: None Of The Above + |||/ +V: 2313 00230fd5e1d4c86edfefc517d88a8a6421826b429fd2eb50ba9671c127897e6a +V: 1234 00a38267cbca56da01221dc49c7e8a0d5c0c92ac6943736c47301b76f07fee55 +V: 1234 0104ae27ece19d7ec3c44235f5cf3d7f261da7199b90517045bfd1a9f9e2815b +V: 122- 01615815a269fe2204dba1f2ed62646b2c88406f3de4b4a6146360e1636b17fc +V: 1243 017d19eb2a0ec8f22b4f0da0903679e2a029a6ffd6fe7ac97247bb0ce7cd308b +V: 1123 02b68510f34e9dd4001b022630c1db0963d2dc5b631ecee71640c0d6d620b8cf +V: 1324 03587b33eddc978bfb281892a55551b723f72871791cfb3713f697dda46ce303 +V: 1324 03ddd8e7530ad80a89a0e8ae3bc09fb8d136b565a478dc506521c097cdccc39e +V: 1342 045f85c8bc3ae81c942f50e0683a31a2bbe34d3fde4adb6f211879aa87542e4f +V: 1423 0553d6b166e9cdcfc40b02a8d616fb0595b0c0365d4dae15355410b123169aac +V: 1234 05b31a059b06d5bfccba09fdb392cffb7e27451eb0b8a3cef66fa9ad99313a40 +V: 122- 05de119d845c3db2dc4f32cdf997408da48fc73f8a8de783ac7999c16c4fb466 +V: 2314 062c6f02a8458937f0f462010854b2292527a09f38d9fa82df990c0934ad8f03 +V: 132- 06b5c7c8f84fb0c88ad55c734c2523873029ff932e4d49b4f0fb5f95648da397 +V: 1--2 07b7623628d5dde1d3ced9ad560639940578dfaccf3739e50e4e7058111afa53 +V: 21-3 085dce24b409845a4deb8460237634ae3fc239a27b69ec08b6eb15b4f276efbc +V: 1243 087d057b2e69670448fd5f60d428f2b93cd3ae3f2cad1dc18bcb8daf85c5bff2 +V: 1243 08ba708a7bcd34d0a08dbff732ad6ab00349b77504507681514c993c1f8d3545 +V: 132- 0ba19a57a9598d8a755ce61da4a55acb4131a924c0491db78333b1649f72ec0d +V: 1132 0bab6662fd04a87d3a456da6ee1e965268a50301d2edb835c58d28daa863b4da +V: 1143 0bdb370e94eec3d92784bc19826c2e6221835c90660b8264e62bf14d68245e9f +V: 1--2 0cec914c29299b7a40084cb19207869baeb3f135bbcc86259802b85c8c2de03c +V: 1342 0d24800b8d57e28b571546ee17f184870bfcc4cef7cc8faf8b3ed1debc5d3c13 +V: 213- 0dc034010b256c0b1b72fa96d0ab7beba78d90a4d708d32d2a4495c453824e65 +V: 1243 0e5ab2229dfc9666e94a6c21f266744030383e4aa6c590f56e7236b447920fc6 +V: 1423 0ea1e5a3edec6da8fbf88ed662a19e6caf5891460f2fcec767f625e0134a4249 +V: 1432 0f3a6fd53289be2a20764c36f4a9ddb6b6561505b449d12d44f246ca3880cd77 +V: 12-- 1051b91e22d54b50e70ec2ec9eb90a7ebbb55f4795784d84bd60fa06d27baf3a +V: 1324 13bbd22d648f39b52e28e39324efbed56c107030187f9fb836828eb72b47cc5e +V: 2-13 1453adb5e5cb9adb37fb879054672c81466d37795b01e2163a6de88832623927 +V: 132- 14dc579f84696e02c6da84960ff5057d101ab52612caebf1bf82079b6035455f +V: 2134 151130777ad486910fcdaf47d71340d7bc97885e9e445551cbbf3965cbc4153b +V: 1432 161e7cca0fc2ed16289b488734a1743d85d66bbd548a10200dad251abe20c745 +V: 1432 186217749f7512384f01aae7e3b02f5b48387b8ea72b46bfcb112b446cfdf0d6 +V: 1234 18d533341ce06a37e6328fd7bf557b03e09ace5556d2e671c9eb1b976acddfca +V: 1234 1a096393a22ed2bfdb6cf02b59d9ba83057a50b6f339439a7d16e4da17c9e0bc +V: 1112 1a0ded7512e931634e34a6bd70079e7ba5dd8ce8dfd5e632850ce3ef17f14722 +V: 2143 1a126954cec275899d8ee55beebc50648d7ac4e7585890073152e69f107ba689 +V: 1332 1acf0431d98eb0d6888afd5f94713552d2118ee2a70f77a3618f9b584192ec30 +V: 123- 1b3bf8eebc3b4ae959f844f7ea40cf538f5cc04bdd231c5113f98be72f6d8912 +V: 1132 1eb417dd3c44eb7f69625af204d89c232c4b0e620fae9ec3f1b93d2b9844a416 +V: --12 1edc3a57f6b9919d80de8befecc9435ef9cd93ea52d52e445cacee0acdea213c +V: 13-- 1eeea04ed2aa31e13e343cd124cb96fda976ef90d533d6fac2415ba23c187482 +V: 231- 1f0b97ea0043978b4095ea3a58fd02fee5cde226a24b8c1b84d498df2c1a6620 +V: 21-- 1f38b144ef16134d85c49c34b5baa25cb541125c39e8d77ab1ff840e3ac4a0ab +V: 1-1- 1fd42b5fbb1400cb2b8815e9d595fb4a8de78336e3ae2c32fe7b972a08cde8bb +V: 1234 2069c55c2c2a39879251ee28085ea7fcafd474c0a9adf71a7cf05710a5c09db5 +V: 1243 216c76dcfd63d942e1453951d5ec89ce43e5f1e579a99bf5b232cae308915694 +V: 1423 21e9f11aa81008acb5ed4651cde7444e988f535929ee2bf48509f9f5a02a574a +V: 123- 23117fe84f1e2e4438d1637961f6eeac605b931477fcf8b41656b97f32032c89 +V: 1432 23b10cd32ba0ff66b69720d5dbeca6c4c93139a826d240825d808a91f2a64d28 +V: 1234 23ce2496abaa5eea4c66d1a93937b6062eb0449cef6262036279e5bc74267936 +V: 1234 2501bd56f7321c5658adad5bf40e62fe833e81f6eaf8573ed9db03ede7e29572 +V: 1243 260d9f85b8f240eade7ac80b4129b2e422cd76b3c965c61766671d7e5ff87fbb +V: -213 26620d1f5308853e02685ec9fa36185eca36d7e2c9183b037ebdc2ca28e62472 +V: 132- 26db6a1d63640b11d2c0f5a44e09c746aa506ad049d5cc5508bc1e2309a7d914 +V: 1243 284834e16622976c9ba3feae7caab326255bc937b547e4cb0a5db9b641847fa7 +V: ---1 292ba49a9d13ac6688dde6c06284b1f8e9a122ca974b6e64bdfb8a5a8ef16792 +V: 132- 29ddd123d7c1d7228b8f04a7c357b8bd525b0b985d1164e0666b6ab624e121d8 +V: 2143 2bc79ff7b56ba1b5f973e2d686cf0fe8284ed02192e56b336aff541a2f9cfaac +V: 1324 2c19f12a63d779d18bb786ac528ad8703e3d273fa766f9572de7c5d92c076d87 +V: 1243 2c309b4c136043e04cf5956a6c9bb8b730572112d1abc4898ef48d4b49dcdfea +V: 1324 2c4cbc9fa4f9ea291ffc2d22598a7f0425524824d39adc4424a91ffea815b4dd +V: -214 2d1a15cde460e780bbab7864858e05bf946229909053ca74c1c740b358d4bd2d +V: 1234 2eba3f1a686253d92f5b87c05946bba17bf3ef377cc99549416300ffe68bd119 +V: ---1 2ec9d0c0596db6bdb00c776f8f5a984946af44a2d223e99f03495e8d97e2a9bd +V: 3142 3090dc887814dca97535dd715044fb156ac3924beb40e278c9604ec3b094c73c +V: 1234 3173f1d70ffa85e2f68a7c8ef19d4cd7f51cc1a361a89dac928800d195027b9f +V: 1243 3184d63ca3cf69e8ab4b6065786da9675325ab781462f052a82e3903d68a3273 +V: 1243 33ee6bed02afa4a2b68099485168b759bd05ba9265f88f139b71cec1c7a3ba24 +V: 132- 37ad76134fdb50a50a2ec09baab6e0bb3e1c94c0055e8a0b2ac2e53659f01121 +V: 1432 37c2766c4d7c94ce4f3645656d814ac1b91d802740d159af0c6d623f9bc58789 +V: 1223 381b844d8cb7f3e4422b8a41e6a370416eff97d58040ef9d68ffa212beb47f84 +V: 1--2 39181f9cb392f8827ec6156b704a5c625c0eafc59356042ab9adbfe636ea4a61 +V: 1243 395bd339c374ce48ccb2c25626345d63286354c4e65bdc50390884fd860e8a26 +V: 1--- 3ad0fc89ce243ce38d98b6cc68df05ba7593f1a71dfd4986b1861d11f0b8454b +V: 1324 3b9d015bc558b6b18d66b1fe1d0fc6a2476b0612e051462653ac24d300a59537 +V: 132- 3bdbc10ffc8965aaa8364978c47e6c939bf3ed6dc6ccd2f76fddc116dfb35aff +V: 3142 3dea431f16502a73772a3d92d86e321a2a33d8f0477f0c1ae627012844d052b6 +V: 2143 3e7cb35db49524a7bdb03497210ea62402fd77ba6e7d50ef0ac9ab19faa5ccc0 +V: 132- 3f376d5d675041d3cf20c7ae80409363a26a2474d1b9182f44fd5537d7738a7b +V: 2134 3f831cbb195e8130e3d4d7b430703598e2e1d1280232b5e798c164017cdd79dd +V: 321- 3fdb669d233eac37cd3b4814f2492b1db3b00d9b7d58633a5901e19849d56d30 +V: 1243 3fffa918e99d969287ecb666e02d1ed0d94ed05d42554434f62044d1a151c1e8 +V: 3142 400458b4cb458c603567dac49b656a878c3fc92ad12e806e89d6f8c009e3b1ce +V: 3134 41dd69104f361489e952a4dd09cfe48e53db931769329e2bd0b0ea8ddbb06a40 +V: 122- 41ee96f092b29cb1c0ca37e5e35b468470b9895656650a375b4d6904082be689 +V: 1332 41f26f6bfc07601b29a5b37c5ee991cf64cd25b2aed7b48bfcf8a10e9684cb7a +V: 1223 43036914deeaca5a3192703254620fc558953937b86289681ad1443fee066e43 +V: 1224 44b7f1a5b9808acf6349f218abf41c6ffbc7c89e9a12d4dd2f1420dee05ba7cb +V: 1234 4512c7af3d347c0ac67661a0b7b118f129147b814bed56c99d1253c2fad1ba8e +V: 1224 477d6b7e8b19ff5ce5a992a6cdf2ad237d96304ade46c2c81ecfc99158e1d4e5 +V: 2134 47857bc01cfcc6e4650335007de8bc071b806ea92a2fd023ab566d0e89aa5464 +V: 1324 4912aa46d364ea1c951b35f95155bce5a8f2fd7510fe5310b898b80c54e317ae +V: 1324 491b838d26c32c9fa15b6a7f79c428cbcca0b4cdc9bbb8c5602b0471bcc82e46 +V: 1223 49d8a2109ccf6f4f0337a0675d866c7b9fc1eb8f8d603d57424ec7999c2c2141 +V: 321- 4a8af63961802228c24ca6c12ce8e9314ac2e47704133286c1534f76d85ce8c5 +V: 1243 4b2aac61a4d2fe9c81a3c4dce5b447d15f092cb348d8ba41311dfcdd28db2256 +V: 2143 4db60fead16f807ad0179aafca04ccd016b467451c5f7d69b0ac9c66cfa9be17 +V: 1223 4e9c0fce8c1d0a7135de0385d6bce3e96426477e4c3603ba426477e9d37b9f69 +V: 231- 4f7c1ced6a867a03ef9930f976d24877bbe8967d93e04df5f5a5d0019960d802 +V: 3412 4feaaa2111dd4e68de721ffa3d38d73efda5dffa4a1f6b1e5db373bef286d9a4 +V: 1--- 50704d439bb5fb104218313f34ab8af85e33bf2cde0d5c9e1d265297af6df010 +V: 12-- 509dd76a0698daad30c206b70dc20fad0b8e0b257a1b4fc0ef4c22870de3cb33 +V: 3142 50e8b2b548e000c6c799e9c5ccb8e695d307e4b939036837f19a0e3543c6669d +V: 1324 51635961ab65280f91b5dffa400d40ac4be172f79992b9854b196f764810fd79 +V: 1243 516f46fb19c7f540b47920f89d016b20ef0bd9ce109fb9e3560e6148aeaf80ba +V: 1324 518bb1cfb457acc983e07c75c225e6c580227e5b7eb58d15c2a4951fa3b24eff +V: 1342 51eed5a592fe0f6b602cd1b930744bbdf3c2acbba1b713cceeb0a47b41a61bfa +V: 1324 52072e9dfaa3e288ac7c6c4e996d9d213191a3b7e1ce41f35cba0f26c278a39a +V: 2143 5221af94b2dd98c45448b21f3fa1d647f06dbff91b3c2f5bb670e863406656e4 +V: 122- 524776b066a0246ea93c565f1124db9712c24295ae372040f6bf117b9c128c47 +V: 2431 5448422e27b967db46fe1025488ede682b1861069c2dda98657d400e6e0831a3 +V: ---1 547fb0314fe14791b18accfbfc2c565dfeffa209c16d9fe8bc4e1adadba54d20 +V: 1243 55fcc1800eb8048ba76308cb27ef8c04012a661a192f56f49eeaa18201a62dda +V: 1234 564245f302908cd54b512d64f179090fd31a8503b8773abbfea9d8e9e02cdcbf +V: 213- 575afc0d3ba0ca08d6586d010dc1c359fe9367ceec56103293385fdca96f959c +V: 1234 58ddb039c66074ac1690358db20491c6f50623d47fcc828c3dec927e4d621fb7 +V: 1111 591cb1fdc78455e36cd85e2f8bcddb74736def1f5ae7ad477fedef1e1fd8b3d8 +V: 1243 5a1175ca0601f5c174484f13d1236881612dfd5fbf322e72d6e2a1b511149616 +V: 1--- 5af419c1c3716030574ed08c976752e861f386e62e61fa7d3a5de889f527f745 +V: 1423 5b3b2777c80f107e3e7aa44b96dc8a3eb8499ca77c2bc8c3f84910d89c02d0f0 +V: 2143 5b6250931b5f647bd4cfa5509ffb998b42c6675797ac3abd0aa1da567f7a695d +V: 1324 5be337340d15c2d240e38fb5c71185e32bab25eb937a95cbc38bd904aef11d4a +V: 1243 5c39a41afe845b06980019ef07aace6e9c64352eeb77a071b532b81a66f32f5e +V: 1234 5cc6847c1882bc189c1885132781e4a48326d88feb2f58245e7cce405f4592ce +V: 3142 5d656881e0c3159be593b204c7ea776f44e0848b4923eb3f486e165609771608 +V: 123- 5db083f1f2e5b1e9c2744a103b87dc3821b7b1e1c04366f9316f39f387fdfeca +V: 2143 5ddb66fa11786f5e4c5310d62830f2207449b14f5c9e3fa2483c172d179cd17d +V: 312- 5e66c5958e186f66966ccedef22cc44c4f951c6b9370d4981318809b5353a258 +V: 1324 5f5656e1f7eb9f716682473619da394ceab35a2f057b6b710038e641e68c9364 +V: -1-- 5f995e23ffc5d0472a8c9c47e31d0d89b616619026af164cf9a825d8bc0ba316 +V: 1--- 60df9c45703d3070b2478958f39d1920e5f8ab47132bf166127d1b02cf1dc3d0 +V: 1432 60fd60a3e86b21489df394bd077a9c2ae852f7eda146e4854a3e979484cf7325 +V: 3441 611be55bcd57fc89c016bae02331ddba792f218ad1d8a19434f374e5ddedd6b3 +V: 12-- 6122856a4782805b2e6344f0b46b02303f630fcf48e051103e24ee1c742b0e0a +V: 1--- 61a109bf6db7dd040e3321cd335c4ead5d479901a51cc58de3c408272d722310 +V: 1234 638ff677bb694b99a45da3187b45146660b7c48a796a24add68188ead7d729b4 +V: 1342 641bcca8c7a20e1668dc94658f01c85c40e068d66e4f386d359b5e7191cf8fc5 +V: 3421 642682889d4ad50f32e25ee327d181bf04a17396a1763efed6a709672eb81a60 +V: 1423 64b2289a6d4be7c4bd71dbe24332e7cc998d7402f7eb2ec83f52353892f5794c +V: 2413 64f36c07e169e52fe1194e3a579e2572eb76ae2e4f6ef5f43e0b5b4d8f42e3ab +V: 2134 6567d243a933775e2ed9295b6119b3d0c7f7083621e3345aa231eb44d1d74327 +V: 12-3 65727b46046d08cc287a31437d1151926d633c56a59aa6c739d9504746302fae +V: 3132 65782607cad77a18d407affd55b7598698cb538aec207e461384ecd9fa907108 +V: 1234 665c01ea8f42470faea4865677c8e6f907fd2d7263f5f41c3af967c65b7ac87f +V: 2134 66651c00d9cdf52c46d3d38fb19970f605d1b352aa21fd8ee69ac3e8d7bb338a +V: 1243 668fa18d7a1d0dd597365487faf797bdfb83f8871290bcb35c240b2cb5de7d52 +V: 1324 670842dc2ea834a83df2179525537054e5f99c483ba1f3279b5ea2e77f75bf2d +V: 3214 675bbed303062069bb8e1484b551ffc198a723f4fd735f49515c964545bcfefa +V: 1234 67a766ddbce1a86891a9f15d4a01b5190da02ee9258368bb71d4e8bff0f70aa3 +V: 1134 67f771b1156d5a82b59187c4e4a490d8d4d5e88f383f9995067828ae91919333 +V: 1243 68f436c1a53db55420d006a73e9777ed2cad57b7fe9bddf0c3400014f0b309fb +V: 132- 693fd519a6ea39a355d655b1d985ebdad5dba4c5fb8806dfe84de5c09492316f +V: 4321 695317bcaa6cde855e4f2c47ec690e8c95e737ef4df95337e886c8b5acab27eb +V: 3-1- 69a8ef1bfb1a6f8ad9c26f5647e646e722d8d99f645a8651cb83823c2573a77b +V: 231- 69eeec23bc5161193d250ee139c8813d20a81ba524cfa783df1db781a22af54c +V: 132- 6a76e47a0e3f9868345e1fb532009514439407361fd48fa24cf4634ea2622120 +V: 2314 6b281305b6bf76b1f7edd6a6f26718b058990d09057bfa9abed1b2927bcd4aab +V: 1123 6bf7daa3756b8cbc45f65571bee50bc2b5f7c86c69d7d3da4e6cef1771736e95 +V: 123- 6bfa769a65845ee681a42d48cebd8c61697c2eaeec5def8091b96b63ed6b184d +V: 1243 6c4fd1bde48da1ab15e570dc4978c605c287440bea8b1031869f17d709265a9f +V: 1234 6e38c35452e2468af44eb307c96bc04f4306db04285706332fddce6284943049 +V: 1243 6e533b84daf0ac9b80a21249d00cc31b6e5b8b16ed2d5291975365f782ce9e8d +V: --12 6f3128090510e3b3a749a3da3718a347690b17515e472287c00424693a6c94f4 +V: 2331 6fce63ac5677b2432e969dfad5cd27369c163f2f8dea4bf9c28434b888104441 +V: 3421 710dc7db44eb6af16776c9e32155f45568a5132c1cf29f4298e453115e25456d +V: 1234 72392dc6cbdf61b2d0c35c654e1fc7c0f3a6821b5ef821942483eb620b438744 +V: 1243 725540f8ffde4cba4f5a77efb11154a68d42d8d1a17a953b8cf11340120aa7d8 +V: 1332 74c31d4c0ca6fc37392e93076f999398112c795c5d765455499a7559631ab47c +V: 1234 7777671d528f4c7acdb7857c3ea933a0504a9a08d9b24c6b8f500efc51fea4fb +V: 1324 7b59a47801e9b0852184c6521151992f8be634e272d2f7e53864e8de1ea87afe +V: 1--2 7b75c039d6aba1b5a61556bd4bcdba732a4c4922336b51340ab5f1154cbfc370 +V: ---1 7cfe95f01abb22402733c37c9654f19f3baf90233f515390eea05a2aea4fb9c6 +V: 1324 7d36731d43b81ef1a40193be3d227cc2c2f8e3137b8f383d011914e9c97e3a0f +V: 122- 7de6b2b78e6f33ee68a83885ec411e052c07f2a22e7a4c54fa103a716615694a +V: 321- 7e7da318d8ee61ddc5be7ab6ea5f16e8066ce1c60d40b78f7a58061370aacbb3 +V: 1442 7e94db0e97920f35e5616f635d18f7e3cb2d3c19fb4929c3419abd62c3fb161c +V: 341- 7ea7456c3b1b212f6754b9cc99a395d4256e81148d1e95ee930fb8b05b6f70ac +V: 1423 8033bfc957830b6f1e8d739736b19fe0db88dc159ca2ae6d7d3652fd27d40a44 +V: 1234 821d21fa4ef2127d73d1d9f1c3006d5a0dd51db4d0d687a1303ff4dadbb12dbd +V: 2331 82b981f25f52c7269dfa90351113c063db8aac32b8857c1ae8feaf27b0539683 +V: 1234 82e5a90d81c2846febd1674c8c2dfa46b9a677b1a6e6cae56d33cd2ca0c331e5 +V: 213- 83190b8340ed97e2e24862865c0ceef1a8231a8c57256909aa879fb62615f507 +V: 1234 83ec9391805a79332864d6ff4d6d7b541a6b36fd2c185e00594125e601672c6a +V: 4313 84f851c39fdfa82eb08b62fb6d881d50d20f226fdf5c77d00241aa0a876430b4 +V: 123- 8598965da7900f506c7385a33ca24cd9c321606e383b16f86031fd1c395ef616 +V: 111- 85ee8de9db19c908d4107d0fe81a43bd685ac2d56280354006b11ce3c4ad6372 +V: 1234 861f87d31834385501ff9e5a452443878c02fd7385c7b9e8cdce92c2dacf40e7 +V: 1234 866c240c9c28f0e2d31f11d925aab7d183eb724c4d0aeb83be07c05cd1d332d8 +V: ---1 86d1fc4713fbcca183523391c7f5497cfc4be98a5b2dd11aa6b727e1dbd5492f +V: 2314 86fdf98bf698febeb33300bc0e36942618f9e21b1885d5c77a0239898009748f +V: 1223 8931a8b26a2fcf6e39aee320db7ed3b15aaeb2c6c28fa41d642359001ac9ec0e +V: 1423 895443c7eeb83d121711181194ba5b8ec69eb5a97532e8b4839e9693b205071f +V: 2314 89c4b0f8625d54c44fe6d0d7b5d28606dafd36cdc0fe10bf77080e6b5b65233b +V: 1233 8a3d51b9c0a1249f5a4f42de84079e946b50a5a553cf6e575166d83685421086 +V: 1324 8a850f90b5282c48474e15f91b5503888c8d806ad07f6e75fee314c47f37ceb1 +V: 3124 8c07321c8a9c9247976f8179082a1fc5adc87201fbec0e327e89864bdf8a6ad4 +V: 3214 8fb98206cf3b14222eaac4b25c94a238236fab634ed0784bf0af1f7b7040f60a +V: 1324 900018d3dd3d39d7c592411ed5b483921e936a02901da2720f0062765ed70e53 +V: 1324 937c6eb3e558e859d4021507cc44e8bc8a0a8df7db2bea30bb141ea4f8c72b57 +V: 1--4 945705fa99e567f0f695c204737f76de576254d659f1e34e5042fb8a1da79117 +V: 213- 94c4e731e175fb1ddf4836cc9cc78c1ce93a953ee6888bc9a1438bfcb4574eaa +V: 1--2 95299541aff780d727f9b4014ca7d85b36e5fc822df919dac720408527cac2d2 +V: 1332 9619afb74f4bd92c8a8fd73bd89fa0cec98e555bb6621da1318e572b6372a4fa +V: 1324 9628e21866b0e71806d52d43f31716867d22320b88189203749ee4419757ad23 +V: 1234 9658c14a9d998f97728e9ac84bc6928e4efb6889d0a5c43e378bb3022b760e24 +V: --1- 966920501d6f5f38a7e8a5a1d9fb52339b72e6fd30263c63a95ed892b4713287 +V: 1342 974dbdc06aa63fa6a578f5a53811b3c176fd96c1c00691ed4aff0329a5e880e7 +V: 2221 9872b34803c05770209bde3a060f1f292e1e7cb88b44dffeddad41b0085783fa +V: 1234 99547a1d62c9268b50d18055e16547756960c6eedb1e0dca8457090de3d27d55 +V: 1432 995a9465cefdca35c49cf15e5ebb3cb9e8b28921aa87486a56cfbf0b991b0fd8 +V: 1223 995c49eafc96472d28dd64c0ce12ee2b95e17bbc4cfd50c5f4a3b7598e83c272 +V: 1--2 9a7722ac85e84660801705efc6435dafdb3ae761c261ac90c4cc4f30a9304e14 +V: 1234 9b0838f5891eeef1df87f1dd5971dbd32a42573927292fd4fb0c60258225199f +V: 231- 9d2ad16596a2fd273d94f3c44197ecc2ecd84317e7aed88dc40fa9615c7ff5df +V: 1233 9dbcd73dd95c769e47b95cdb7c09f199959775143529b113edcb904ca1413532 +V: 1132 9de1c25d174edd05d7c30535517226a1bc52a498d126c4468fe690fa6f524729 +V: 2143 9f18109633b37d11dca369c31ae3c41a0f82f102ae33ac609e10e96db53cb9ef +V: 123- 9fa2683057f83695a190663fc0e2f38fef669e0f74bbb5904d99b64aa8287d6d +V: 132- 9ff7950887b5103d5d8d251623ab7e887c153c7de4eaa15602d1ffb962873afb +V: 3312 a0a8965e15509244e9b4a87f5b05750dc92366561899f8fb5af3b1b1a5d29436 +V: 2143 a18fdad5c8c4dee869e3f8aba9d53dcf03dd32476d088a3ee6b0f33054a75deb +V: 1324 a209cf013669c8a13f31b2cfb23f0889d55f89f7ca8701c3dc0fa5d4536f1ced +V: 12-- a241c88b992936b69ea99a1de315440ec715a9bc1a05fb8580a503e7885cf912 +V: 2331 a2488cc3e9e4a8e927173650d665c62f414ba90819fe2d9af5877ff454afe305 +V: 3132 a4ab151cad3197e7b9e80b60732f2283c2f8b0d4e3a3fa716f54fac325d2f30c +V: 1243 a4f4c16e3d740d1be9bfdea40f6678813b157d7d91b61e8bdbf8f3812be9f291 +V: 1243 a63f9d3a23b6159fabf45874c00bbe178c0d5bdbcd726c19a09b816a3eeabdc1 +V: 132- a79eef235c0530dccab92913cc6bdc8ae62dd1a27e9c5f753d25a25865daee67 +V: 2431 a7e963cc242119d8583d541830fb3e6b5de7d0c28175e0a402793332fe7c74b5 +V: 2413 a8293b1c65fbed212bcdd2199c95ed1b323792dba3be913df45e29bffdf3fba3 +V: 132- aa40bab0f832f3c0b6f8e3bec2c08a7dedc48ae527d6063dd1d7c72bf07b5f45 +V: 4444 aabd62acd6c8ca258a0a716b3c9fa9ca24cd3e3c4b8c77404da2d3883fb81e12 +V: 1423 abddd5dc2e3cfa1acddb5f386992f4a73316d758337d99053df21ae1fcf19ddc +V: 1234 acd0ff84b59571e14b98fe995697060303c8279e3e58d4269a2ab0cbeb040b4f +V: 1-2- af0789d52fa246a8133df11e17bd4b60710dc3a3c70313fdb7b8b4f037f8bd48 +V: 1243 b028e3bc08d2c8fd5374b8ec5b65587cf501bff569c3bfb3c7088db54c581829 +V: 2314 b0bf1709ce7a978839d463c9e354ee0ae8e5b099c522baa1d083668e21dbb350 +V: 1243 b17d9dd78a6d16650581746d56331a67e9e9d5a8790b1068e930d17b0c85ca16 +V: 2134 b1ff74d6019227f860cfe394d33112090bc0fc8ab525e424f0ac393d2ed4477a +V: 2134 b2118cff55fc5c3c40fc1f35a718e87bf0ce4326bb01287c4a840feaf1266084 +V: -1-2 b2647fdbf96a8f9d5ba0722e1fc0575951f61e8e39f3360ec238b2bec4f31f43 +V: 1324 b2ad4279d784eeeb41cfbaed5396fc412421361cfcfe9d8f7a39773f2e47aeb4 +V: 1234 b2b8f07e2a6eb4e60e76eb00f86c721f0d52a7cd3ad9687893fd1697a7b0ec32 +V: 1332 b3282e1ace028119fb12431daca45c798ab54916a986d55c92b8106c9fa5b90c +V: 1234 b50c1b62fce7ce8befc477d9781ad6f036f2d281ad8d5424a94e03f7d5ca7d84 +V: 1324 b521fc2e8281a53bf3694c66d2a6394547bb9dfe1c838790053ba5282abcaeed +V: 1234 b55b7afeb537fa892f93996fb4b69a72ba07ec6c82529006b91faf931560af28 +V: 132- b57abea86a397f16384d0c86d1049feadb7f1b639fd4c96f7454e44c70b64d0f +V: 1342 b7d63d41965da9e483d6660806f44084f60cf6d9d3ea91a687bd85e19e8e9f3d +V: 1-2- b82f85d10f4ff97f0542815b64cf1c8884694adb1414b76b9db77e8ffdf784bf +V: 1324 b94990cb249ad507a294da3da2301e6741cf697f9ebb61a2b2698a75ac18ba38 +V: 1223 b9c6ab7341fe79f71bb68ede0b97bd58280187f71199522bc294ecf93a81ec24 +V: 123- ba3c715b7f3b714394e82092c2a21cfb43e1af1c087f9c5cd4198bbfcb0c457d +V: 1324 badbdd9cc1f2cc99e903193ea3d6cd81a00195a3d1d243247621b3122497d70d +V: 1332 bb8a5f01bc35cbd83f156c2310699bd7f9fe6438855af3742a94977fe01305bc +V: 3124 bc815b156abac6bed6bbc8560fd33e8df18097b097b8d2752a17ffcc73bf0e92 +V: 1312 bd20e4e7057b9ddf916da20b67a64fef4307b3e8b5e6ed1fa2d3eb83cbb2eff8 +V: 1-2- be58de50429f21fb6f069cb6191349a50567cb9f48eb0fa190db668280df79df +V: 2134 bef8ffb42ba8cf0973962d2320ae055e1f420e5f5e36aadb3bb15c1e28deb18d +V: 2341 c007d9c20bc7e5b1a97948315dd815ef95d0825ab755725b217e5c8350d342c3 +V: 1243 c06f5f8b93020633dba0dc34fcd4ce9e1bb82add3f59248f227c0dd20a157acf +V: 1--2 c15d433a517bc451df1d3b5e72371c11998657244593fe3df192d129e3b837f3 +V: 3-14 c2b77edc07f3af06a1e158ce55ae36ba0f3dfaa6e55f6071f7ee6b4eb381fefb +V: 1243 c3134402f85fe8ef5abebefb70654a0ff3bc4b7ed8d567b3f927189e33df6716 +V: 2--1 c55768b26d1e5a6b48442fc898fab4b2bf7777046b03cc663415c8fcf46dab84 +V: 1342 c6f41abafe4e8977af77d294a40b57ae3b931c716b11cfde884c7662a71b9645 +V: 1332 c7817348270724eb35c3a76de22498161d509359d95e4f160be337bb393f1c63 +V: 122- c838f17d30d8b2b957cf49775a468cee64f3e13974577710fed81041741ff3f2 +V: 123- cabfc096aa5ecf42e23a1391b3a236a50ced3da47c93a2e8bf8cdd67ef87ecab +V: 1--- cae83e3c79fcee59017aa741439df1b6f8192f57e0aa527ad3ad626b049da031 +V: 1224 cccd5b5f7310307167c51806e0989a134c91a3a593779f4e4534afb5b62cdc2c +V: 2134 cd0c5ae8ab69cc44596d24a7c7300e9fdf1cb4e4f9b8f07440146c4096045440 +V: 1324 cf056bfbb7436ea53e6ac51517f1ec5e75736b81b98e7b87b5ecc110b643e65b +V: 1432 cf1bbd2e9ed2306307631135af81e850739483890ce405ab9a0a0c655b8fe738 +V: 12-3 d018469cb6a8ec6c80b6ff436279907e97ed5500944e9688fd62d039581e406d +V: 123- d109ca02ebf28bb1f45a55b47c5ce4febbc2d74e4609528bb8e0894bc2762aef +V: -1-2 d1ad0078098f332d54243a6eb45897aaf24c7c8398001638c96a243cd127bd93 +V: 1243 d33ad007842ddb9a16a13b5b8042d267134553e700b59eef9febf109180e69d8 +V: 1234 d453d3a6732b845fae67430993f354acd5aa442a45f3fc57aa3a6004c45d1f10 +V: 1342 d46078625095fb5238c79e7015caa7e452d336458161def6a3ff0efafbd65b1e +V: 1423 d53e129f0e8de61631867d85c4f0e340bbc3bda995fe6f7174bf52ae3ab4a818 +V: 2143 d607c68d0baf141c404266193f73cf16a7b7d7ed3b4e7bd96c3f4fb1d9cdcc86 +V: 1--- d6612fd623b2aa5dc1c74dcea2b25eea828695a0c929fff8a5c191801167aaa7 +V: 143- d742a7a26ab23a4302d6f6a1538aea3d7a4a533bd8288f18623858d0f75d6b47 +V: 1123 d76800ee67faed96e04fc79fee0d277c477989055bfb583c4472ec22d1e16fef +V: 2143 d7eddb6bc259a081e6f260855ff203f377d520cfbb62acfa691059fb787aad16 +V: 1243 d808a6229a41c31a17fe17e4bf4eca2bb754ce45c825b29a8d1c5fe2a009a84b +V: 1234 d882c5a434b340dcfa9b4b1ab418d9248bed5ce5e3b47899b36e86de615c691c +V: 1243 d9cc4807186147d165e8502076dae5c0d6c34e8c81f3206b950cefd80bd06358 +V: 1243 db07d43c82af5e797695293a78398712c0a1015e14a74dfd438faaa69b8b9920 +V: 2134 db4417cafae7cf1fd8961b7d4722e116c583496183cb8b5b9ff54aaffd6d5011 +V: 1324 db66a3144ca4164b78d755e5e1d12c69b727c76117200c83433ca37056a64c8e +V: 1123 dba989d730342e8f9187a40724a9db2b51fb6d4f8829f676f2da1ff514f54b3a +V: 1324 ddd015d992805d78b0763837e13f6b12ed2ce98af64192cfcb0b4f7403e7ff77 +V: 1234 de0b883f0c62ab73d4b4cf51a02036828f3e50b828f419576d0f7de78163af9f +V: 1243 dee08b1cad06eed24ea5fa8296b3517c2d4e49e7509d7ccc70cee8f9baf767b6 +V: 1234 df7b23c1e82ab9ef09f4f160624a62bd31ed66254f384b379a7dc25197c0deb8 +V: 1223 df942ed4951f6e2c44063ef5bfc2948e9e5c60f8e7228ca41b47f5d2d293cf50 +V: 231- e05b0c0307eb24eef7e3ad3246e3bb3a564688addcf03e26ec5b5bc89ebf8b4a +V: 1324 e0fe58087a6be3fba15bca5d426c758bf64482089d0ef1b7e7f88ccff26e2bab +V: 1324 e13eefeb5a11fa820951493620925cddb1f0b43fe32d57dc3620b0b0d0f5a965 +V: 1243 e2c239f9dfb960af46c10d63c0382c3b34a69324caef0f0dc198c81e40fb3d2a +V: 1332 e3031b38717fab10a755fda6a8c1afe8327f0cb56cb7f5bdec4f66417565d599 +V: 1423 e3786f46aec93a7d0d4833805fca583c6b3adc30535e7b6a6fd7f9914d091304 +V: 231- e3c40e28c6ecd3496a9d0727c627ea82bb2cd473531dd343fefd20d051a21efd +V: 1442 e3d83ca355450bc9ef01d9ecd4cc524df6d26d3e1952a34d0e8cf6fadb0ce273 +V: 2143 e4f7c618fd859a049309344bce6dbb1ac82d026994da31dbcdead954343e0bb3 +V: 1324 e645b42cd8df792e993298820401e1dc1608bfe5aeb0c3197f1a5056312332b1 +V: 1--- e6dd2e7a9026ae00224333bf733f012aa8ca9a62abe942749235bd20d5732c67 +V: 1243 e6ea5d80ace12a22759ab3a522df6efa2a1cd8b65e651fc6c5aa148683613b43 +V: 2143 e71eced655b7e055c1fa964d4f59a0a0d45469e6aedaed95ef6eeae89eb16162 +V: 1324 e7c84202b6389bb8a00c3a1560712ab3944a9e5ea197264ba034a79d1dcbefd6 +V: 1332 e9510ca600935ac70c5cf34f833bbc79b30ada9578050e878835bcffaeb711fe +V: 2134 e993930a9d4138d33e2d035f2cf200346b232fcc211d520d057b020b741f7241 +V: 1234 eb5039de58c0f0822ae1325a4098e84b2ee7d9c7dade967daea76ddf9665fa84 +V: 2134 eb990ffe5423e829f3c574c70ea2dd85afc54fab693c6f56a516de0cce7e73bf +V: 2413 ec8450663968fa79c74d718f8fd297508f0a9a61fa20986d60923029e0140245 +V: 1342 ed09fb58f17d81ec1445759c591947a1f3c3020cb94e1da95b1c24d3d43edf8d +V: 4231 ed814e84f97eeef24ed75d4e378ebe94e7989dbd556ad49e2d10969a110d8896 +V: 122- ee831b442ffe9f78fabd636db19a3971fdbc6fc7492c35f8751332486e8da4b8 +V: 132- eeaa12e828b2adc09d2153a9af5f3c9d65b67e7c2c9dce28a0818bbde1eb56b0 +V: 12-3 f0292d5ddfb3b59c6614f781096d4287e1080a42b64b7eb0cec0fd1a28c15f6c +V: 2314 f1038f35b8a74878b31a2fbca061b284be5ab60e685c538215f8f02ed859260a +V: 1432 f1b88d5505eb7886a7a6c307ca283f24bfd4ac6f347ef79b16b9446e690600a7 +V: 132- f2fafb29cc8b773ff4e8eca280b80c4025e48ffbfbc35c754d865b21ab8581a3 +V: 1324 f39a4e391326f31e8eef0e1edcf606478b5860a7d2e68684d106966706abeae5 +V: 1324 f4f5af10cc6750cbe27630ee0102e6d650e8a8da75b3adc439dc86faf8e39970 +V: 123- f69965dba3253f4cae3de1e910981d04e4c215af4257d58e231b9919f7cdc398 +V: 1123 f6fef1d957364983901f9fd70cd1784ee91c9944ce5403f6db2261f53a2449cb +V: 1223 f7d9f266a8860da5cae2f3f1985d0ec40ef8ba2646a7320eba6dcdf80e5ce8fc +V: 2143 f7f4751708682d25f8926bbb5e3225456ac39256cf37a66320890513008875bf +V: 2143 f9fda8051473bb5c506fa8cb3bf18019939003f85391f95252f5d3fefe613c65 +V: 1--2 fac6dbd7d23e398c32b281e7549f1cec2bc2afe057f22b112f2228ff70942cff +V: 1243 fc0d80e20c401136bf89eff2f55714179ca1d78e30eb33dee4a63258edab60f9 +V: 1242 fc11eaee428c4a0f42c77994c72efd96f25a79db17f250adca5b1e7847caed40 +V: 12-- fce80a45bb9ec929c592dd441baa7d89ec08c6c511faead56fe35fcb454dec8c +V: 1324 feb645c3d70b34a37ac206a023afbbe9947fa9ef0b03695927c52a1a8499446d diff --git a/include/Condorcet/Tests/src/Tools/Converters/DebianFormatTest.php b/include/Condorcet/Tests/src/Tools/Converters/DebianFormatTest.php new file mode 100644 index 0000000..e9757e5 --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/DebianFormatTest.php @@ -0,0 +1,115 @@ +setDataToAnElection(); + + self::assertSame(339, $election->countVotes()); + self::assertSame(1, $election->getNumberOfSeats()); + + self::assertSame( + 'Jonathan Carter > Sruthi Chandran > Brian Gupta > None Of The Above', + $election->getResult('Schulze Margin')->getResultAsString() + ); + + self::assertSame( + unserialize('a:4:{s:15:"Jonathan Carter";a:3:{s:15:"Sruthi Chandran";i:201;s:11:"Brian Gupta";i:241;s:17:"None Of The Above";i:267;}s:15:"Sruthi Chandran";a:3:{s:15:"Jonathan Carter";i:0;s:11:"Brian Gupta";i:49;s:17:"None Of The Above";i:168;}s:11:"Brian Gupta";a:3:{s:15:"Jonathan Carter";i:0;s:15:"Sruthi Chandran";i:0;s:17:"None Of The Above";i:69;}s:17:"None Of The Above";a:3:{s:15:"Jonathan Carter";i:0;s:15:"Sruthi Chandran";i:0;s:11:"Brian Gupta";i:0;}}'), + $election->getResult('Schulze Margin')->getStats() + ); + } + + public function test2020_Explicit(): void + { + $election = new Election; + $election->setImplicitRanking(false); + $election->setNumberOfSeats(1); + + self::$debian2020->setDataToAnElection($election); + + self::assertSame(339, $election->countVotes()); + + self::assertSame( + 'Jonathan Carter > Sruthi Chandran > Brian Gupta > None Of The Above', + $election->getResult('Schulze Margin')->getResultAsString() + ); + } + + public function test2007_Implicit(): void + { + $election = self::$debian2007->setDataToAnElection(); + + self::assertSame(482, $election->countVotes()); + self::assertSame(1, $election->getNumberOfSeats()); + + self::assertSame( + 'Sam Hocevar > Steve McIntyre > Raphaël Hertzog > Wouter Verhelst > Anthony Towns > Gustavo Franco > None Of The Above > Simon Richter > Aigars Mahinovs', + $election->getResult('Schulze Margin')->getResultAsString() + ); + } + + public function test2007_Explicit(): void + { + $election = new Election; + $election->setImplicitRanking(false); + $election->setNumberOfSeats(1); + + self::$debian2007->setDataToAnElection($election); + + self::assertSame(482, $election->countVotes()); + + self::assertSame( + 'Sam Hocevar > Steve McIntyre > Raphaël Hertzog > Wouter Verhelst > Anthony Towns > Gustavo Franco > None Of The Above > Simon Richter > Aigars Mahinovs', + $election->getResult('Schulze Margin')->getResultAsString() + ); + } + + public function test2006_Implicit(): void + { + $election = self::$debian2006->setDataToAnElection(); + + self::assertSame(421, $election->countVotes()); + self::assertSame(1, $election->getNumberOfSeats()); + + self::assertSame( + 'Anthony Towns > Steve McIntyre > Andreas Schuldei = Jeroen van Wolffelaar > Bill Allombert > None of the Above > Ari Pollak > Jonathan aka Ted Walther', + $election->getResult('Schulze Margin')->getResultAsString() + ); + } + + public function test2006_Explicit(): void + { + $election = new Election; + $election->setImplicitRanking(false); + $election->setNumberOfSeats(1); + + self::$debian2006->setDataToAnElection($election); + + self::assertSame(421, $election->countVotes()); + + self::assertSame( + 'Steve McIntyre > Anthony Towns > Jeroen van Wolffelaar > Andreas Schuldei > Bill Allombert > None of the Above > Ari Pollak > Jonathan aka Ted Walther', + $election->getResult('Schulze Margin')->getResultAsString() + ); + } +} diff --git a/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A1.HIL b/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A1.HIL new file mode 100644 index 0000000..99538b7 --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A1.HIL @@ -0,0 +1,384 @@ +10 3 +1 2 9 7 0 +1 2 7 9 0 +1 2 9 7 0 +1 2 9 7 0 +1 1 2 5 0 +1 7 2 6 0 +1 9 8 7 2 3 0 +1 9 4 1 0 +1 9 10 2 6 7 8 4 5 1 0 +1 9 7 2 0 +1 9 7 8 0 +1 9 8 0 +1 9 6 7 2 3 4 5 1 8 0 +1 9 8 7 2 1 10 3 4 6 0 +1 9 8 7 0 +1 9 8 7 0 +1 9 8 7 0 +1 9 1 3 4 7 10 8 6 2 0 +1 9 7 2 1 5 0 +1 9 6 0 +1 9 8 7 0 +1 9 8 0 +1 9 10 2 3 0 +1 9 10 2 3 0 +1 9 3 4 7 0 +1 9 0 +1 9 6 7 0 +1 9 8 1 0 +1 9 7 2 6 8 4 0 +1 9 7 10 0 +1 9 1 4 3 7 8 2 10 6 0 +1 9 1 3 0 +1 9 8 6 0 +1 9 8 3 7 0 +1 9 1 3 0 +1 9 0 +1 9 1 7 3 8 0 +1 9 2 10 6 5 0 +1 9 7 0 +1 9 4 3 0 +1 9 8 4 1 3 10 0 +1 9 2 7 8 0 +1 9 8 3 6 7 2 0 +1 9 0 +1 9 8 3 6 7 2 0 +1 9 8 7 1 0 +1 9 10 7 4 1 2 6 3 5 0 +1 9 3 1 0 +1 9 10 7 1 0 +1 9 4 1 2 0 +1 9 2 7 8 3 0 +1 9 4 8 1 2 0 +1 9 7 2 10 3 1 0 +1 9 4 3 1 7 8 6 2 10 0 +1 9 4 3 1 7 8 6 2 10 0 +1 9 8 0 +1 9 1 0 +1 9 4 8 1 3 7 0 +1 9 4 1 0 +1 9 8 6 3 2 7 10 1 4 0 +1 9 8 4 0 +1 9 7 8 0 +1 9 8 4 0 +1 9 0 +1 9 1 8 3 4 0 +1 9 2 7 8 0 +1 9 2 7 3 4 5 6 8 1 0 +1 9 2 7 4 0 +1 9 2 7 8 6 10 0 +1 9 4 3 0 +1 9 7 3 1 0 +1 1 9 2 4 8 10 0 +1 1 9 10 4 8 0 +1 1 9 10 0 +1 1 9 7 4 5 2 10 8 6 0 +1 1 9 3 0 +1 1 9 7 6 3 0 +1 1 9 10 3 0 +1 1 9 4 3 8 10 0 +1 1 9 4 7 0 +1 1 9 8 2 3 7 4 10 0 +1 1 9 8 2 3 7 4 10 0 +1 1 9 3 0 +1 1 9 3 0 +1 1 9 3 0 +1 1 9 10 8 4 3 0 +1 1 9 5 3 0 +1 1 9 5 3 0 +1 1 9 5 3 0 +1 1 9 8 3 10 2 4 5 6 0 +1 1 9 10 4 0 +1 1 9 10 4 7 3 0 +1 6 9 2 0 +1 6 9 10 8 0 +1 1 5 9 2 7 10 0 +1 1 5 9 2 7 10 0 +1 1 5 9 2 7 10 0 +1 1 10 9 4 0 +1 1 10 9 0 +1 1 10 9 2 3 4 5 6 7 0 +1 1 10 9 2 0 +1 1 10 9 2 3 4 5 6 7 0 +1 10 9 7 3 2 0 +1 10 9 4 8 6 3 0 +1 10 1 9 0 +1 10 9 8 0 +1 10 9 8 0 +1 10 9 2 0 +1 1 7 9 8 3 4 6 10 5 0 +1 1 7 9 8 6 3 2 10 4 0 +1 1 7 9 3 0 +1 1 7 9 0 +1 1 7 9 0 +1 1 7 9 0 +1 1 7 9 6 2 3 0 +1 1 7 9 3 0 +1 7 6 9 0 +1 7 9 2 0 +1 7 9 8 0 +1 7 9 8 0 +1 7 1 9 4 3 8 10 0 +1 7 9 3 0 +1 7 9 3 0 +1 7 9 3 0 +1 7 9 3 0 +1 7 9 8 0 +1 7 9 6 8 0 +1 4 8 1 3 5 6 10 2 9 0 +1 4 5 7 10 1 2 8 0 +1 4 5 8 1 0 +1 7 4 2 9 8 1 3 6 10 0 +1 2 9 7 4 8 0 +1 2 4 8 0 +1 2 4 8 0 +1 2 4 8 0 +1 7 0 +1 6 7 0 +1 1 10 5 0 +1 10 1 0 +1 10 1 0 +1 10 0 +1 10 0 +1 5 10 0 +1 1 5 0 +1 1 5 0 +1 1 6 5 0 +1 1 0 +1 1 0 +1 1 0 +1 1 4 9 10 6 0 +1 1 4 10 5 0 +1 1 4 0 +1 1 4 0 +1 1 4 0 +1 1 4 5 0 +1 1 5 4 0 +1 1 2 4 0 +1 4 9 1 0 +1 4 10 1 0 +1 4 9 6 0 +1 4 9 6 0 +1 4 0 +1 4 0 +1 4 0 +1 4 2 7 0 +1 4 9 7 0 +1 4 1 9 0 +1 4 1 9 0 +1 4 10 5 0 +1 4 5 9 1 0 +1 4 1 5 0 +1 4 0 +1 6 7 4 10 0 +1 10 4 0 +1 10 4 1 0 +1 2 4 0 +1 1 4 3 6 10 0 +1 1 4 9 3 8 0 +1 1 4 9 3 8 0 +1 1 4 9 3 8 0 +1 1 4 3 6 8 5 0 +1 1 5 4 9 2 3 7 6 8 0 +1 1 2 4 3 0 +1 1 4 5 8 10 0 +1 1 4 8 7 0 +1 1 4 5 9 8 0 +1 1 4 8 0 +1 1 2 4 5 8 10 0 +1 8 9 2 0 +1 8 3 1 6 0 +1 8 10 3 1 2 0 +1 8 2 6 0 +1 8 2 1 3 4 0 +1 8 2 1 3 4 0 +1 8 2 0 +1 8 2 0 +1 8 2 6 0 +1 8 2 7 9 0 +1 8 3 1 6 0 +1 8 1 9 4 3 2 10 5 6 0 +1 8 2 7 3 0 +1 8 1 5 0 +1 8 7 0 +1 8 9 1 2 7 4 0 +1 8 1 7 9 0 +1 8 10 7 6 9 0 +1 8 10 9 7 6 0 +1 8 7 9 10 6 0 +1 8 1 7 9 0 +1 8 4 2 0 +1 8 7 9 0 +1 8 7 1 0 +1 8 1 3 4 2 5 10 9 7 0 +1 8 1 3 4 2 5 10 9 7 0 +1 8 2 7 9 0 +1 8 3 1 6 9 7 10 0 +1 8 3 1 6 9 7 10 0 +1 8 1 10 0 +1 8 1 10 0 +1 1 8 4 0 +1 1 8 6 4 2 3 5 10 9 0 +1 1 8 3 4 0 +1 1 8 9 3 4 2 6 5 7 0 +1 1 8 4 0 +1 1 8 9 10 0 +1 1 8 4 9 0 +1 1 8 4 9 0 +1 1 8 4 9 0 +1 1 8 3 9 0 +1 1 8 4 0 +1 1 8 4 0 +1 5 8 1 0 +1 6 8 9 0 +1 1 5 8 0 +1 1 5 8 0 +1 1 5 8 0 +1 1 5 8 0 +1 1 5 8 0 +1 10 6 1 8 3 5 0 +1 10 6 1 8 3 5 0 +1 7 1 8 9 10 0 +1 7 8 3 0 +1 10 7 8 4 5 1 2 9 6 0 +1 6 7 8 9 1 3 0 +1 6 7 8 9 1 3 0 +1 6 7 8 0 +1 6 7 8 0 +1 2 8 10 0 +1 2 8 7 9 4 0 +1 2 8 7 9 4 0 +1 2 8 0 +1 2 8 0 +1 2 8 7 0 +1 2 8 7 0 +1 2 7 8 9 0 +1 2 8 9 4 0 +1 2 7 8 9 0 +1 2 8 0 +1 2 8 0 +1 2 8 9 0 +1 2 8 0 +1 2 8 0 +1 2 9 8 0 +1 2 9 7 8 6 10 5 4 3 0 +1 2 7 9 8 10 0 +1 2 7 9 8 10 0 +1 2 9 8 0 +1 7 2 9 8 0 +1 7 2 9 6 8 3 10 5 1 0 +1 7 2 9 8 4 0 +1 4 3 6 0 +1 4 5 1 8 9 10 0 +1 4 3 8 0 +1 4 3 1 0 +1 4 3 6 0 +1 4 3 1 2 9 10 8 6 7 0 +1 4 9 2 7 3 5 10 6 1 0 +1 4 10 3 1 0 +1 4 1 3 0 +1 4 3 8 9 0 +1 4 10 9 3 1 2 5 0 +1 4 7 10 3 8 1 2 6 5 0 +1 4 3 8 1 10 0 +1 7 10 1 4 3 8 9 0 +1 7 4 1 2 3 5 6 8 9 0 +1 10 4 1 9 5 3 2 6 7 0 +1 10 4 9 1 3 2 0 +1 2 9 1 10 4 3 8 0 +1 2 4 9 7 3 8 10 6 1 0 +1 3 6 8 0 +1 3 6 8 0 +1 3 6 8 0 +1 3 6 8 0 +1 3 1 0 +1 3 1 0 +1 3 1 0 +1 3 1 0 +1 3 1 0 +1 3 1 0 +1 3 1 8 0 +1 3 4 1 9 0 +1 3 1 0 +1 3 1 0 +1 3 1 0 +1 3 1 2 4 5 6 7 8 9 0 +1 3 1 6 0 +1 3 9 8 0 +1 3 7 1 5 0 +1 3 4 10 1 6 2 5 8 9 0 +1 3 1 0 +1 3 1 0 +1 3 4 9 7 0 +1 3 5 8 0 +1 3 1 0 +1 3 1 0 +1 3 1 9 0 +1 3 1 9 0 +1 3 1 9 0 +1 3 4 5 8 10 1 2 9 6 0 +1 3 6 0 +1 3 9 1 8 7 5 4 0 +1 3 1 5 2 4 7 6 9 8 0 +1 3 8 2 7 0 +1 1 2 3 7 8 9 5 6 10 0 +1 2 3 9 0 +1 2 3 9 10 8 0 +1 2 5 3 1 0 +1 2 3 1 4 9 10 8 5 6 0 +1 2 3 9 10 8 0 +1 1 3 9 8 4 2 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 3 4 9 5 6 10 2 8 0 +1 1 3 4 8 6 5 7 9 10 0 +1 1 3 0 +1 1 3 8 9 10 0 +1 1 3 4 0 +1 1 3 9 0 +1 1 3 0 +1 1 3 9 7 0 +1 1 3 9 7 0 +1 1 3 6 0 +1 1 3 7 8 10 9 2 6 4 0 +1 1 3 7 9 2 0 +1 1 3 2 7 9 0 +1 1 3 2 7 9 0 +1 1 3 9 0 +1 1 3 9 0 +1 1 3 9 0 +1 1 3 9 0 +1 1 3 9 7 0 +1 1 3 0 +1 1 3 9 0 +1 1 3 9 0 +1 1 3 9 0 +1 1 3 9 10 7 8 0 +1 1 3 5 9 0 +1 1 3 2 7 0 +1 1 3 2 7 0 +1 1 3 2 7 0 +1 1 3 9 7 0 +1 1 3 8 4 0 +1 1 3 4 0 +1 1 3 0 +1 1 3 5 0 +1 1 3 0 +1 1 3 9 0 +1 1 3 7 0 +1 1 3 4 9 0 +1 1 3 4 0 +1 6 3 4 1 0 +1 1 6 3 0 +1 1 10 3 0 +1 10 3 5 0 +1 10 1 3 4 2 5 6 9 8 0 +1 7 1 3 9 8 0 +1 7 1 3 9 8 0 +1 7 3 4 0 +0 +"Candidate 1" "Candidate 2" "Candidate 3" "Candidate 4" "Candidate 5" "Candidate 6" "Candidate 7" "Candidate 8" "Candidate 9" "Candidate 10" "a1" + \ No newline at end of file diff --git a/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A3.HIL b/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A3.HIL new file mode 100644 index 0000000..e2946f1 --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A3.HIL @@ -0,0 +1,992 @@ +15 7 +1 0 +1 8 0 +1 8 0 +1 8 6 0 +1 8 6 0 +1 8 6 0 +1 8 0 +1 8 6 0 +1 8 0 +1 8 6 0 +1 8 0 +1 5 11 8 0 +1 5 11 6 8 1 0 +1 5 8 6 14 0 +1 4 11 14 0 +1 4 6 8 14 11 0 +1 4 6 11 1 13 15 14 7 5 3 2 12 8 9 0 +1 4 6 11 8 5 0 +1 4 6 0 +1 4 9 12 14 0 +1 4 8 0 +1 4 3 13 0 +1 4 6 10 7 11 14 12 0 +1 4 8 6 1 14 11 0 +1 4 11 0 +1 4 6 5 0 +1 4 6 0 +1 4 8 5 0 +1 4 8 6 0 +1 4 5 3 0 +1 4 5 3 0 +1 4 6 5 11 7 10 12 13 8 9 14 15 0 +1 4 11 3 6 0 +1 4 11 6 5 14 0 +1 4 5 6 11 14 0 +1 4 13 3 5 7 10 11 14 0 +1 4 8 14 1 0 +1 4 6 1 11 0 +1 4 11 0 +1 4 5 6 7 11 14 0 +1 4 2 7 8 5 13 14 9 10 3 1 6 15 11 0 +1 4 8 3 6 11 9 14 15 2 1 5 13 7 12 0 +1 4 0 +1 4 14 11 0 +1 4 6 12 0 +1 4 6 5 2 1 13 12 11 7 3 8 9 10 14 0 +1 4 8 3 5 11 14 0 +1 4 6 5 0 +1 4 6 5 0 +1 4 5 6 8 3 11 0 +1 4 11 6 10 15 0 +1 4 5 7 11 0 +1 4 0 +1 4 6 8 0 +1 4 6 3 0 +1 4 11 8 6 0 +1 4 0 +1 4 0 +1 4 13 3 0 +1 4 5 0 +1 4 6 11 0 +1 4 6 3 0 +1 4 6 11 8 7 1 0 +1 1 8 4 11 0 +1 4 8 6 0 +1 4 6 11 10 0 +1 4 6 7 10 14 11 3 5 8 9 12 13 15 1 0 +1 4 1 6 11 0 +1 4 8 6 5 11 14 0 +1 4 6 8 11 0 +1 4 1 6 11 2 8 0 +1 4 11 8 5 6 3 7 14 15 13 12 10 9 0 +1 4 1 5 0 +1 4 6 2 0 +1 3 1 7 13 15 0 +1 3 4 12 13 11 8 9 0 +1 3 0 +1 3 2 0 +1 3 6 4 1 0 +1 3 4 10 0 +1 3 11 6 10 14 0 +1 3 11 8 0 +1 3 8 6 0 +1 3 2 7 15 13 0 +1 3 14 5 10 13 0 +1 3 0 +1 3 6 7 0 +1 3 2 7 15 0 +1 3 13 1 7 15 2 9 10 0 +1 3 13 0 +1 3 8 0 +1 3 6 4 0 +1 3 6 11 0 +1 3 8 0 +1 3 4 10 14 0 +1 3 7 11 0 +1 3 1 0 +1 3 14 6 8 0 +1 3 0 +1 1 0 +1 1 13 2 3 7 0 +1 1 4 6 13 11 14 0 +1 1 3 10 0 +1 1 3 2 15 6 0 +1 1 5 0 +1 1 10 2 3 7 0 +1 1 13 3 0 +1 1 2 3 6 10 0 +1 4 0 +1 4 6 1 11 3 5 0 +1 4 6 11 1 3 0 +1 4 5 6 11 12 15 0 +1 4 0 +1 4 11 14 8 0 +1 4 0 +1 4 8 11 0 +1 4 6 10 0 +1 4 11 8 0 +1 4 11 6 0 +1 4 6 11 1 8 13 2 14 0 +1 4 14 11 8 0 +1 4 11 5 14 6 0 +1 4 3 1 5 0 +1 4 6 11 0 +1 4 5 6 11 0 +1 4 5 6 13 14 0 +1 4 5 11 0 +1 4 3 5 8 14 0 +1 4 5 8 3 11 0 +1 4 6 10 11 5 12 8 0 +1 4 11 8 6 5 0 +1 4 5 6 8 11 14 15 13 12 3 1 2 7 9 0 +1 4 6 11 10 13 12 14 0 +1 4 7 9 15 0 +1 4 6 10 12 0 +1 4 3 6 8 11 0 +1 4 6 11 7 10 12 14 0 +1 4 0 +1 4 0 +1 4 0 +1 4 6 1 0 +1 4 0 +1 4 0 +1 4 6 5 11 0 +1 4 6 8 0 +1 4 12 6 1 3 13 0 +1 4 12 5 0 +1 4 8 6 11 0 +1 4 12 8 5 11 0 +1 4 13 5 1 11 3 8 0 +1 4 9 15 1 3 2 5 7 0 +1 4 6 11 14 0 +1 4 14 11 0 +1 4 6 11 1 7 0 +1 4 5 14 0 +1 4 8 2 7 0 +1 4 11 15 8 6 5 3 10 0 +1 4 6 8 11 14 0 +1 4 6 11 5 10 12 14 0 +1 4 5 6 11 14 12 8 7 15 3 10 0 +1 4 0 +1 4 6 11 12 14 10 8 5 13 7 15 3 2 1 0 +1 4 6 8 11 12 13 3 5 14 0 +1 4 8 1 0 +1 1 4 6 11 12 13 15 8 14 7 5 2 9 10 0 +1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 +1 2 0 +1 2 7 9 0 +1 2 7 0 +1 2 0 +1 2 0 +1 2 9 10 0 +1 2 1 0 +1 2 13 9 10 7 0 +1 2 1 4 0 +1 2 0 +1 2 7 1 3 10 13 0 +1 2 6 10 0 +1 2 13 6 3 8 0 +1 2 1 7 15 0 +1 2 7 0 +1 2 7 1 6 10 15 0 +1 2 7 9 13 10 0 +1 2 0 +1 2 9 6 0 +1 2 5 10 0 +1 2 5 10 0 +1 2 1 7 0 +1 2 0 +1 2 0 +1 2 7 9 0 +1 2 10 7 9 0 +1 2 1 0 +1 2 9 10 0 +1 2 7 0 +1 2 0 +1 2 10 9 0 +1 2 8 6 0 +1 2 13 7 0 +1 2 7 12 13 9 10 6 0 +1 2 7 10 9 8 0 +1 2 7 9 0 +1 4 8 12 0 +1 4 6 8 13 15 5 10 0 +1 4 6 11 13 0 +1 4 11 12 0 +1 4 6 1 8 0 +1 4 8 11 0 +1 4 6 8 1 0 +1 4 6 7 5 11 0 +1 4 1 6 9 11 3 5 2 7 10 12 14 15 13 0 +1 4 8 11 14 12 13 3 0 +1 4 8 14 3 0 +1 4 6 8 0 +1 8 5 0 +1 8 4 12 14 15 3 6 5 1 2 7 9 10 11 0 +1 8 9 5 11 0 +1 8 5 6 0 +1 8 11 5 0 +1 8 11 0 +1 8 5 2 6 7 9 10 0 +1 8 9 5 0 +1 8 12 0 +1 8 4 5 11 14 0 +1 8 5 0 +1 8 5 6 11 0 +1 8 6 11 9 0 +1 8 2 7 10 9 0 +1 1 14 15 11 0 +1 1 7 15 0 +1 1 3 13 0 +1 1 6 2 15 11 7 3 10 0 +1 1 3 5 13 7 11 0 +1 1 6 0 +1 1 7 0 +1 1 3 7 0 +1 1 4 6 0 +1 1 6 8 9 15 0 +1 1 9 10 13 3 5 0 +1 1 8 5 0 +1 1 6 4 11 0 +1 1 13 3 0 +1 1 0 +1 1 6 0 +1 1 2 3 7 8 15 10 13 0 +1 4 6 12 13 0 +1 1 9 2 3 6 5 0 +1 1 3 14 0 +1 1 2 7 0 +1 1 0 +1 1 11 13 0 +1 1 2 7 0 +1 1 15 4 6 7 8 3 2 9 10 13 11 14 12 0 +1 1 3 13 0 +1 1 3 13 4 0 +1 1 2 15 9 0 +1 1 3 15 0 +1 1 13 5 4 6 0 +1 1 4 5 13 0 +1 1 4 6 0 +1 1 8 12 0 +1 1 10 2 8 0 +1 1 3 13 0 +1 1 3 13 0 +1 8 11 2 0 +1 8 5 4 6 1 11 14 7 10 3 9 15 0 +1 8 5 0 +1 8 3 13 10 0 +1 8 11 5 9 6 0 +1 8 7 11 0 +1 2 0 +1 2 7 10 9 13 3 8 6 0 +1 2 6 7 10 9 0 +1 2 6 0 +1 2 3 1 7 8 9 13 5 12 15 10 14 4 11 0 +1 2 3 5 11 0 +1 2 10 9 0 +1 2 6 7 10 0 +1 2 7 9 1 3 0 +1 2 6 7 10 1 13 3 0 +1 2 1 4 13 0 +1 2 7 0 +1 2 10 6 0 +1 2 7 0 +1 2 13 0 +1 2 8 15 11 0 +1 2 0 +1 2 3 1 13 0 +1 2 1 10 0 +1 2 8 0 +1 2 0 +1 2 7 5 1 3 10 9 0 +1 2 1 0 +1 2 7 0 +1 8 12 0 +1 8 5 9 11 0 +1 8 2 3 6 0 +1 8 3 10 13 1 0 +1 8 11 0 +1 8 5 11 9 0 +1 8 14 5 0 +1 8 11 5 0 +1 8 7 6 0 +1 8 14 4 0 +1 8 6 15 0 +1 8 4 3 6 1 2 5 7 13 11 10 15 14 9 0 +1 8 3 7 0 +1 8 12 3 0 +1 8 11 5 9 0 +1 8 11 10 0 +1 8 14 10 0 +1 8 5 9 3 11 0 +1 8 12 0 +1 8 12 6 0 +1 5 6 8 0 +1 5 6 4 11 8 0 +1 5 8 0 +1 5 11 15 14 8 0 +1 5 8 7 10 3 11 0 +1 5 11 8 0 +1 5 9 11 0 +1 5 8 11 2 6 14 0 +1 5 6 0 +1 5 8 12 11 0 +1 5 6 15 8 11 0 +1 5 4 6 11 13 1 3 8 0 +1 5 11 4 0 +1 5 4 3 0 +1 5 14 4 0 +1 5 8 0 +1 5 8 1 0 +1 5 8 1 11 12 13 9 2 3 14 7 10 4 6 0 +1 5 6 11 0 +1 5 11 4 0 +1 5 8 11 0 +1 8 14 0 +1 8 6 4 0 +1 8 14 0 +1 8 5 11 0 +1 8 14 0 +1 8 6 11 0 +1 8 5 14 0 +1 8 11 5 0 +1 8 5 6 11 0 +1 8 11 14 12 0 +1 8 11 0 +1 8 6 12 0 +1 8 6 14 0 +1 8 6 14 0 +1 8 5 6 4 11 12 10 13 0 +1 8 7 2 1 15 11 0 +1 8 3 12 0 +1 8 5 3 4 9 11 14 0 +1 8 6 5 0 +1 8 11 3 14 0 +1 8 11 9 5 0 +1 8 5 9 11 0 +1 8 11 0 +1 8 5 11 9 6 0 +1 8 11 6 12 0 +1 8 11 5 4 6 9 14 15 0 +1 8 4 11 5 6 12 14 15 9 0 +1 8 6 4 3 0 +1 8 6 5 0 +1 5 12 9 2 1 15 8 6 7 13 10 3 14 4 0 +1 5 11 15 0 +1 5 0 +1 5 11 0 +1 5 8 11 14 7 6 0 +1 5 8 14 1 11 0 +1 5 4 11 15 0 +1 5 0 +1 5 7 11 0 +1 5 6 1 7 8 15 13 11 12 4 3 9 14 2 0 +1 5 6 8 0 +1 5 0 +1 5 14 4 6 15 8 11 12 3 0 +1 5 11 0 +1 5 7 8 11 14 13 4 1 15 12 6 3 9 10 0 +1 5 11 6 0 +1 5 6 11 0 +1 5 6 3 11 12 0 +1 5 0 +1 5 6 14 0 +1 5 6 11 12 0 +1 5 11 8 14 0 +1 5 6 11 4 14 8 0 +1 5 14 8 0 +1 5 8 11 12 0 +1 5 6 14 8 7 0 +1 5 8 11 13 6 15 1 7 3 0 +1 6 9 7 13 15 14 8 0 +1 5 4 8 1 0 +1 5 7 2 0 +1 5 6 14 11 12 8 0 +1 5 11 4 13 6 0 +1 5 8 9 0 +1 5 6 8 0 +1 5 6 14 0 +1 5 11 8 6 1 7 2 4 3 9 10 14 15 13 0 +1 5 11 8 4 3 0 +1 5 6 8 14 0 +1 5 11 6 0 +1 5 11 8 4 6 0 +1 5 8 13 0 +1 5 8 11 6 12 0 +1 8 5 9 11 0 +1 8 5 11 13 6 1 0 +1 8 11 5 9 0 +1 8 1 4 0 +1 8 14 11 0 +1 8 5 11 0 +1 8 5 11 12 0 +1 8 9 11 5 6 0 +1 8 6 11 14 0 +1 7 6 2 5 4 8 14 0 +1 7 6 12 8 0 +1 7 13 6 0 +1 7 10 8 0 +1 8 1 3 0 +1 8 4 6 14 15 0 +1 8 6 14 0 +1 8 6 11 0 +1 8 6 11 0 +1 8 11 14 0 +1 8 6 5 9 0 +1 8 11 0 +1 8 6 0 +1 8 5 11 14 0 +1 8 0 +1 8 5 9 6 4 14 0 +1 8 11 0 +1 8 5 11 9 0 +1 8 0 +1 8 11 0 +1 8 1 6 0 +1 8 11 6 0 +1 8 5 11 4 12 14 0 +1 8 9 12 0 +1 8 5 11 9 0 +1 8 0 +1 8 11 4 0 +1 8 10 12 0 +1 8 6 5 14 0 +1 7 4 8 6 5 0 +1 7 10 6 0 +1 7 10 6 0 +1 7 10 4 2 13 3 0 +1 7 2 8 10 0 +1 7 0 +1 7 8 12 0 +1 7 11 14 0 +1 7 0 +1 7 1 4 0 +1 7 6 0 +1 7 5 12 0 +1 7 8 15 0 +1 7 4 3 1 6 13 14 15 5 2 8 9 10 12 0 +1 7 6 10 11 1 0 +1 7 1 3 2 6 13 15 0 +1 7 11 12 3 4 6 5 8 0 +1 10 2 6 0 +1 10 14 6 11 4 0 +1 10 14 7 6 4 12 11 0 +1 10 0 +1 10 0 +1 10 9 11 0 +1 10 2 7 9 0 +1 10 8 7 0 +1 10 5 6 0 +1 10 14 6 0 +1 10 7 2 0 +1 10 11 14 0 +1 9 8 6 4 0 +1 9 14 8 0 +1 9 12 6 8 2 0 +1 9 10 12 6 14 13 1 0 +1 9 0 +1 9 10 2 0 +1 9 0 +1 9 10 8 2 0 +1 9 8 12 0 +1 9 0 +1 9 10 12 6 14 13 1 0 +1 9 8 11 14 3 4 10 0 +1 9 8 11 14 3 4 0 +1 9 8 11 13 3 4 10 0 +1 9 6 12 14 8 0 +1 9 12 6 0 +1 9 6 12 0 +1 7 2 3 1 13 15 0 +1 7 1 2 0 +1 7 15 3 1 2 0 +1 7 6 1 0 +1 7 4 2 0 +1 7 1 3 15 13 6 2 0 +1 7 10 6 4 0 +1 7 15 3 0 +1 7 15 1 3 13 0 +1 7 4 6 10 15 14 11 13 8 3 12 1 5 2 0 +1 7 9 10 0 +1 7 8 2 13 6 3 0 +1 7 2 9 0 +1 7 0 +1 7 1 2 3 10 9 0 +1 7 15 5 0 +1 7 2 6 0 +1 7 2 0 +1 7 2 13 0 +1 7 8 5 0 +1 7 5 8 0 +1 7 1 2 8 3 0 +1 11 6 5 8 0 +1 11 7 15 0 +1 11 13 12 6 14 0 +1 11 15 6 0 +1 11 9 7 4 3 14 12 0 +1 11 7 0 +1 11 4 1 0 +1 11 5 3 0 +1 11 8 6 0 +1 11 10 7 2 6 14 15 12 13 9 1 8 5 3 0 +1 11 4 6 0 +1 11 4 5 12 0 +1 11 6 8 0 +1 11 8 12 0 +1 11 5 6 4 8 3 13 14 0 +1 11 6 5 14 0 +1 11 6 8 3 4 13 14 15 12 7 2 1 9 10 0 +1 11 12 14 0 +1 11 8 0 +1 11 14 6 5 8 0 +1 11 8 10 0 +1 11 5 12 14 0 +1 11 6 10 0 +1 11 4 14 7 8 3 0 +1 11 6 5 0 +1 11 4 14 7 8 3 0 +1 11 6 5 0 +1 11 6 4 0 +1 11 6 13 12 8 14 1 0 +1 11 6 5 8 0 +1 11 14 4 0 +1 11 5 6 8 0 +1 11 6 4 0 +1 11 5 6 0 +1 11 4 6 1 7 10 5 14 0 +1 11 8 6 10 12 0 +1 11 4 6 0 +1 11 13 12 2 14 0 +1 11 14 6 0 +1 11 6 12 0 +1 11 8 3 5 4 6 0 +1 11 6 4 0 +1 11 14 6 0 +1 11 4 8 0 +1 11 6 0 +1 11 4 14 6 0 +1 11 14 5 0 +1 11 8 0 +1 11 8 15 14 3 6 0 +1 10 14 15 11 9 4 5 6 7 12 13 0 +1 10 6 4 11 12 0 +1 10 14 4 5 7 6 8 11 0 +1 11 7 0 +1 11 5 0 +1 11 2 6 0 +1 11 0 +1 11 8 4 3 5 0 +1 11 1 4 2 0 +1 11 5 6 14 0 +1 11 4 6 3 14 0 +1 11 4 13 14 12 0 +1 11 4 8 0 +1 11 14 6 5 8 4 0 +1 11 0 +1 11 14 6 12 0 +1 11 0 +1 11 12 14 0 +1 11 8 5 0 +1 11 15 14 12 8 6 0 +1 11 4 5 6 14 3 12 0 +1 11 6 12 14 1 3 4 7 13 0 +1 11 3 8 0 +1 11 14 0 +1 11 4 6 8 0 +1 11 3 5 0 +1 11 12 8 6 0 +1 11 5 14 4 0 +1 11 5 8 0 +1 11 5 6 14 0 +1 11 12 6 0 +1 11 6 7 14 0 +1 11 8 5 6 14 12 1 4 0 +1 11 14 8 6 3 10 12 0 +1 11 5 4 6 0 +1 11 6 14 0 +1 11 8 2 0 +1 11 14 6 0 +1 11 8 6 0 +1 11 5 8 3 4 14 0 +1 11 12 5 6 15 0 +1 11 8 5 6 0 +1 11 6 12 0 +1 11 5 3 0 +1 11 5 6 0 +1 11 0 +1 11 6 4 8 0 +1 11 0 +1 11 14 8 0 +1 11 14 9 0 +1 11 6 8 3 4 5 13 14 0 +1 11 6 4 3 0 +1 11 1 4 7 5 8 9 12 10 3 2 14 15 6 0 +1 3 9 13 0 +1 0 +1 5 0 +1 6 0 +1 0 +1 0 +1 8 5 11 12 0 +1 8 4 0 +1 8 12 0 +1 8 6 14 0 +1 8 11 6 14 0 +1 8 11 4 14 5 9 3 0 +1 8 1 0 +1 8 5 4 0 +1 8 11 10 4 0 +1 8 9 11 5 0 +1 8 5 11 9 0 +1 8 14 13 6 0 +1 8 0 +1 8 6 5 14 11 12 15 0 +1 12 14 0 +1 12 8 6 0 +1 12 0 +1 12 3 4 6 13 0 +1 12 0 +1 11 9 0 +1 12 6 1 0 +1 12 11 8 6 4 0 +1 12 8 13 0 +1 12 14 0 +1 12 8 0 +1 12 6 1 8 0 +1 12 0 +1 12 6 5 4 11 7 10 13 8 9 14 15 0 +1 8 6 12 0 +1 8 9 11 0 +1 8 5 11 9 0 +1 8 11 5 0 +1 8 4 5 0 +1 8 11 0 +1 8 0 +1 8 11 14 0 +1 8 11 5 9 0 +1 8 6 14 3 4 0 +1 8 12 0 +1 8 5 12 0 +1 8 5 11 12 6 4 0 +1 8 11 5 6 0 +1 8 5 4 11 6 0 +1 8 0 +1 8 11 9 5 0 +1 8 11 6 5 14 3 15 12 0 +1 8 7 1 0 +1 8 13 15 0 +1 8 6 12 0 +1 8 4 11 0 +1 8 0 +1 8 12 15 6 14 0 +1 8 6 2 0 +1 8 11 5 0 +1 8 6 14 12 0 +1 8 6 1 0 +1 8 9 12 0 +1 8 5 12 4 6 0 +1 8 5 11 9 0 +1 8 0 +1 8 11 6 3 4 14 0 +1 8 14 5 0 +1 8 6 4 11 5 1 15 14 13 12 10 9 2 3 0 +1 8 6 3 11 14 0 +1 0 +1 0 +1 0 +1 0 +1 14 1 11 6 8 10 0 +1 14 4 6 0 +1 14 6 8 0 +1 14 6 11 15 8 7 4 12 5 13 9 0 +1 14 12 8 6 1 10 0 +1 14 6 8 0 +1 14 6 10 0 +1 14 11 6 0 +1 14 5 0 +1 14 12 0 +1 15 7 2 0 +1 15 3 13 7 10 0 +1 15 12 6 0 +1 15 7 14 0 +1 15 12 11 0 +1 15 11 4 2 6 8 14 13 12 1 3 5 7 10 0 +1 15 14 0 +1 15 8 6 0 +1 15 3 2 1 0 +1 15 11 0 +1 15 7 0 +1 15 6 7 0 +1 15 14 11 3 4 0 +1 15 7 2 11 10 0 +1 13 11 1 6 8 5 10 0 +1 13 8 0 +1 13 1 3 7 15 2 0 +1 13 3 7 15 1 9 0 +1 13 1 6 0 +1 13 12 4 0 +1 13 6 3 0 +1 13 7 6 0 +1 13 11 6 5 0 +1 13 11 6 15 0 +1 13 8 4 6 1 0 +1 12 2 6 0 +1 12 5 8 4 0 +1 12 9 8 0 +1 12 11 5 0 +1 12 8 0 +1 12 0 +1 12 9 0 +1 12 8 6 11 5 0 +1 12 6 0 +1 12 6 8 0 +1 12 14 6 0 +1 12 8 4 0 +1 12 6 3 0 +1 12 3 8 0 +1 12 4 13 0 +1 6 11 0 +1 6 5 4 13 0 +1 6 0 +1 6 1 3 0 +1 6 10 14 8 0 +1 6 5 11 7 4 10 12 13 8 9 14 15 0 +1 6 13 12 11 9 15 0 +1 6 8 12 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 11 10 5 12 13 14 15 7 9 8 0 +1 6 8 14 0 +1 6 13 4 3 5 0 +1 14 6 8 11 4 3 0 +1 14 8 11 3 4 6 0 +1 14 4 0 +1 14 0 +1 14 0 +1 14 8 10 0 +1 14 0 +1 14 5 11 0 +1 14 6 11 0 +1 14 6 8 5 4 11 3 12 0 +1 14 11 4 5 13 0 +1 14 8 10 6 0 +1 14 11 4 0 +1 14 6 4 3 0 +1 14 4 5 0 +1 14 5 0 +1 14 11 6 0 +1 14 0 +1 14 6 7 0 +1 14 8 0 +1 14 3 8 10 13 0 +1 14 4 6 0 +1 14 10 8 0 +1 14 6 10 0 +1 14 15 6 11 10 5 0 +1 14 0 +1 14 10 11 0 +1 14 11 8 12 0 +1 14 8 6 2 0 +1 14 6 4 11 10 5 8 1 0 +1 14 12 8 11 10 0 +1 14 8 0 +1 14 8 0 +1 14 11 4 7 2 0 +1 14 11 4 5 8 0 +1 14 12 15 4 8 7 13 10 1 3 6 5 2 11 0 +1 14 0 +1 14 11 12 0 +1 14 6 0 +1 6 4 10 14 7 5 8 11 0 +1 6 0 +1 6 12 8 3 0 +1 6 0 +1 6 0 +1 6 11 4 14 12 0 +1 6 1 7 5 0 +1 6 8 4 0 +1 6 5 11 0 +1 6 7 10 3 12 2 1 0 +1 6 2 5 0 +1 6 5 11 2 10 3 7 13 4 1 0 +1 6 8 9 10 0 +1 6 11 4 10 14 5 0 +1 6 11 1 0 +1 6 13 0 +1 6 8 11 0 +1 6 5 8 11 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 8 15 0 +1 6 11 2 1 13 3 0 +1 6 8 0 +1 6 10 2 11 7 14 12 13 0 +1 6 4 5 11 14 8 15 12 0 +1 6 12 0 +1 6 11 2 13 15 8 0 +1 6 5 11 8 4 0 +1 6 0 +1 6 3 5 9 10 4 7 8 11 13 15 14 2 12 0 +1 6 8 0 +1 6 8 14 0 +1 6 3 8 0 +1 6 8 14 0 +1 6 5 11 12 14 13 4 15 0 +1 6 10 2 9 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 8 0 +1 6 5 8 11 14 0 +1 6 8 4 14 11 0 +1 6 8 11 0 +1 6 2 3 5 4 8 1 7 13 0 +1 6 4 12 0 +1 6 4 8 11 12 13 10 5 7 14 15 1 9 2 0 +1 6 3 4 0 +1 6 8 11 0 +1 6 11 4 0 +1 6 4 11 0 +1 6 11 3 12 7 0 +1 6 11 13 4 14 8 0 +1 6 4 8 12 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 8 11 4 3 13 14 12 15 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 3 8 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 11 15 0 +1 6 12 0 +1 6 8 4 3 11 14 0 +1 6 8 4 14 12 5 11 15 9 7 0 +1 6 12 8 4 11 14 0 +1 6 0 +1 13 6 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 11 15 0 +1 6 5 8 9 12 11 13 0 +1 6 8 12 0 +1 6 4 8 11 14 0 +1 6 11 14 0 +1 6 4 3 1 13 0 +1 6 5 4 11 7 3 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 2 13 15 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 11 1 3 4 8 15 0 +1 6 5 8 4 11 0 +1 6 8 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 11 14 0 +1 6 4 8 5 11 12 14 15 0 +1 6 4 1 13 0 +1 6 4 2 7 11 13 14 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 10 2 0 +1 6 8 14 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 8 0 +1 6 11 5 15 0 +1 6 5 4 14 7 10 12 13 8 9 11 15 0 +1 6 5 4 14 7 10 12 13 8 9 11 15 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 8 14 0 +1 6 8 4 11 0 +1 6 4 12 11 13 0 +1 6 5 4 11 8 12 0 +1 6 5 8 4 0 +1 6 5 4 8 11 0 +1 6 4 8 7 5 3 14 15 0 +1 6 4 14 11 0 +1 6 4 12 0 +1 6 11 0 +1 6 11 0 +1 6 8 14 4 0 +1 6 14 11 1 0 +1 6 5 3 4 8 13 0 +1 6 0 +1 6 5 4 11 7 10 12 13 8 0 +1 6 8 12 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 14 12 10 8 9 11 1 4 13 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 5 8 0 +1 6 11 14 0 +1 6 11 5 0 +1 6 4 3 1 7 8 5 9 14 12 10 11 0 +1 6 4 13 14 0 +1 6 8 12 0 +1 6 4 11 7 0 +1 6 5 8 9 3 2 0 +1 6 8 12 11 0 +1 6 8 1 0 +1 6 14 0 +1 6 8 14 0 +1 6 4 11 5 12 8 10 0 +1 6 14 9 0 +1 6 3 10 4 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 3 0 +1 6 0 +1 6 7 11 10 2 13 3 1 9 0 +1 6 5 8 3 13 7 15 14 12 1 11 9 10 4 0 +1 6 11 12 0 +1 6 7 2 10 9 0 +1 6 9 10 3 4 1 7 11 13 8 0 +1 6 0 +1 6 0 +1 6 5 0 +1 6 5 4 11 7 10 12 13 14 9 8 15 0 +1 6 11 8 12 5 4 1 13 14 0 +1 6 5 4 8 9 10 11 0 +1 6 3 5 0 +1 6 11 14 0 +1 6 0 +1 6 8 4 0 +1 6 4 11 12 8 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 8 4 11 13 0 +1 6 5 3 8 13 9 14 10 7 1 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 8 11 5 0 +1 6 5 11 0 +1 6 5 8 11 14 15 9 4 0 +1 6 1 7 0 +1 6 7 2 9 10 15 1 3 12 14 13 4 11 5 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 10 14 7 5 8 11 0 +1 6 5 8 11 0 +1 6 8 4 0 +1 6 13 5 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 8 0 +1 6 4 10 11 7 0 +1 6 4 8 11 7 5 14 10 3 0 +1 6 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 1 8 2 5 3 4 7 9 12 15 13 10 11 0 +1 6 8 9 5 11 0 +1 6 7 3 4 0 +1 6 8 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 11 5 3 14 0 +1 6 4 11 7 10 12 13 8 9 14 0 +1 6 4 8 12 11 14 3 5 13 0 +1 6 4 8 12 11 14 3 5 13 0 +1 6 4 11 5 12 14 15 13 0 +1 6 4 11 0 +1 6 8 0 +1 6 13 12 11 0 +1 6 7 10 12 13 3 4 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 14 8 0 +1 6 7 4 3 5 2 14 8 1 15 0 +1 6 4 11 3 12 13 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 14 11 0 +1 6 3 5 0 +1 6 4 11 7 10 12 13 8 9 14 15 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 11 12 0 +1 6 12 8 0 +1 6 2 0 +1 6 1 0 +1 6 0 +1 6 5 4 11 7 10 12 13 8 9 14 15 0 +1 6 4 11 0 +1 6 8 11 0 +1 6 5 1 4 11 2 0 +0 +"1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "a3" diff --git a/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A60.HIL b/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A60.HIL new file mode 100644 index 0000000..14b22ea --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A60.HIL @@ -0,0 +1,200 @@ +6 1 +1 2 0 +1 2 0 +1 2 0 +1 2 0 +1 2 0 +1 2 0 +1 2 0 +1 2 4 3 0 +1 4 0 +1 4 0 +1 4 0 +1 4 0 +1 4 0 +1 4 0 +1 4 0 +1 3 0 +1 3 0 +1 3 0 +1 3 0 +1 3 0 +1 3 0 +1 0 +1 4 3 5 0 +1 4 5 2 0 +1 3 5 0 +1 3 5 2 0 +1 5 3 4 0 +1 5 0 +1 5 4 3 0 +1 5 0 +1 5 2 4 0 +1 5 4 3 0 +1 5 4 3 0 +1 4 5 6 0 +1 5 6 0 +1 5 6 1 4 3 0 +1 5 6 0 +1 5 4 6 3 1 0 +1 5 6 2 1 4 0 +1 5 6 0 +1 5 4 6 1 2 0 +1 5 6 0 +1 5 3 2 4 6 0 +1 5 4 6 3 1 0 +1 5 6 0 +1 3 5 6 0 +1 3 2 5 4 6 0 +1 6 5 4 0 +1 6 4 1 0 +1 6 0 +1 6 2 4 0 +1 6 0 +1 6 2 3 0 +1 6 0 +1 6 0 +1 6 1 4 2 3 0 +1 6 0 +1 6 0 +1 6 0 +1 6 0 +1 6 5 3 0 +1 6 0 +1 6 4 5 3 1 0 +1 6 1 3 0 +1 6 0 +1 6 5 0 +1 6 1 3 0 +1 6 0 +1 6 0 +1 6 1 3 0 +1 6 4 5 3 2 0 +1 6 0 +1 6 0 +1 6 5 4 1 2 0 +1 6 4 0 +1 6 5 2 3 1 0 +1 6 5 0 +1 6 0 +1 6 0 +1 6 0 +1 6 2 3 1 4 0 +1 6 3 2 4 5 0 +1 6 0 +1 6 4 3 5 1 0 +1 6 5 4 0 +1 6 5 1 0 +1 6 0 +1 6 0 +1 6 0 +1 6 5 1 0 +1 3 6 5 0 +1 3 6 0 +1 3 6 4 0 +1 3 6 4 0 +1 2 4 6 0 +1 4 6 3 0 +1 4 6 1 2 3 0 +1 4 3 6 0 +1 4 3 2 6 1 0 +1 4 2 6 3 1 0 +1 4 6 0 +1 4 5 1 0 +1 3 4 5 2 1 0 +1 5 1 6 2 3 0 +1 5 1 4 0 +1 5 1 0 +1 5 4 1 0 +1 5 1 0 +1 5 1 3 6 2 0 +1 5 1 0 +1 5 1 4 0 +1 5 1 0 +1 5 1 3 4 2 0 +1 1 4 3 0 +1 1 0 +1 1 0 +1 1 0 +1 1 2 3 6 5 0 +1 1 4 2 3 5 0 +1 1 4 6 0 +1 1 5 4 3 0 +1 1 4 3 0 +1 1 5 6 0 +1 1 6 4 0 +1 1 3 4 0 +1 1 2 6 3 4 0 +1 1 0 +1 1 0 +1 1 5 0 +1 1 0 +1 1 4 3 0 +1 1 6 2 4 3 0 +1 1 0 +1 1 0 +1 1 3 6 5 2 0 +1 1 6 2 3 4 0 +1 1 0 +1 1 6 5 3 4 0 +1 1 0 +1 1 6 0 +1 1 2 3 0 +1 1 6 3 5 4 0 +1 1 4 2 3 6 0 +1 1 0 +1 1 0 +1 1 3 0 +1 1 5 2 3 6 0 +1 1 3 4 0 +1 1 4 5 6 3 0 +1 1 3 0 +1 1 3 2 4 6 0 +1 1 0 +1 1 3 5 0 +1 1 0 +1 1 6 0 +1 1 5 6 3 2 0 +1 1 2 4 0 +1 1 5 3 4 6 0 +1 1 3 2 4 6 0 +1 1 0 +1 1 5 2 6 3 0 +1 1 4 6 2 3 0 +1 1 3 0 +1 1 5 4 3 2 0 +1 1 3 0 +1 1 0 +1 1 3 2 5 4 0 +1 1 5 0 +1 1 5 0 +1 1 5 0 +1 1 2 5 0 +1 1 3 4 0 +1 1 3 4 6 2 0 +1 1 2 6 4 5 0 +1 1 6 0 +1 1 5 4 3 0 +1 1 0 +1 1 4 3 0 +1 1 5 4 0 +1 1 2 5 0 +1 1 6 0 +1 2 1 3 4 5 0 +1 2 1 3 0 +1 3 1 0 +1 3 1 5 6 2 0 +1 3 1 5 0 +1 3 1 5 0 +1 3 1 4 0 +1 3 1 5 0 +1 3 1 4 0 +1 3 4 1 0 +1 4 1 6 5 3 0 +1 4 2 1 0 +1 4 1 5 6 2 0 +1 4 1 6 2 5 0 +1 4 1 3 2 6 0 +1 4 1 6 0 +0 +"1" "2" "3" "4" "5" "6" "f:a60" diff --git a/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A77.HIL b/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A77.HIL new file mode 100644 index 0000000..6773873 --- /dev/null +++ b/include/Condorcet/Tests/src/Tools/Converters/TidemanData/A77.HIL @@ -0,0 +1,216 @@ +3 1 +1 1 2 0 +1 1 0 +1 1 2 0 +1 1 0 +1 1 3 0 +1 1 3 0 +1 1 2 0 +1 1 0 +1 1 2 0 +1 1 2 0 +1 1 0 +1 1 3 0 +1 1 2 0 +1 1 2 0 +1 1 2 0 +1 1 3 0 +1 1 0 +1 1 0 +1 1 2 0 +1 1 2 0 +1 1 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 2 0 +1 1 0 +1 1 2 0 +1 1 3 0 +1 1 3 0 +1 1 2 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 0 +1 1 3 0 +1 1 3 0 +1 1 2 0 +1 1 0 +1 1 2 0 +1 1 2 0 +1 1 2 0 +1 1 2 0 +1 1 2 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 2 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 2 0 +1 1 3 0 +1 1 2 0 +1 1 3 0 +1 1 3 0 +1 3 1 0 +1 3 2 0 +1 3 0 +1 3 0 +1 3 0 +1 3 2 0 +1 3 2 0 +1 3 1 0 +1 3 0 +1 3 2 0 +1 3 0 +1 3 0 +1 3 0 +1 3 0 +1 3 0 +1 3 1 0 +1 3 1 0 +1 3 2 0 +1 3 0 +1 3 1 0 +1 2 0 +1 2 3 0 +1 2 3 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 3 0 +1 3 0 +1 2 0 +1 2 0 +1 2 0 +1 2 0 +1 2 3 0 +1 2 3 0 +1 2 3 0 +1 2 3 0 +1 2 3 0 +1 2 3 0 +1 2 3 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 2 1 0 +1 3 2 0 +1 3 1 0 +1 3 2 0 +1 3 2 0 +1 3 1 0 +1 3 1 0 +1 3 0 +1 3 1 0 +1 3 0 +1 3 0 +1 3 1 0 +1 3 0 +1 3 2 0 +1 3 0 +1 3 2 0 +1 3 0 +1 3 0 +1 3 1 0 +1 3 2 0 +1 3 2 0 +1 3 2 0 +1 3 1 0 +1 3 2 0 +1 3 2 0 +1 3 0 +1 3 0 +1 3 0 +1 3 1 0 +1 3 1 0 +1 3 0 +1 3 0 +1 3 1 0 +1 3 2 0 +1 3 1 0 +1 3 1 0 +1 3 2 0 +1 3 0 +1 3 0 +1 3 2 0 +1 3 2 0 +1 3 2 0 +1 3 1 0 +1 3 1 0 +1 3 1 0 +1 3 0 +1 3 1 0 +1 3 2 0 +1 3 0 +1 3 1 0 +1 3 1 0 +1 3 1 0 +1 3 2 0 +1 3 2 0 +1 3 2 0 +1 3 0 +1 3 1 0 +1 3 1 0 +1 3 1 0 +1 3 2 0 +1 3 0 +1 3 0 +1 3 1 0 +1 1 3 0 +1 1 0 +1 1 0 +1 1 3 0 +1 1 2 0 +1 1 2 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 3 0 +1 1 0 +1 1 3 0 +1 1 0 +1 1 3 0 +1 1 2 0 +1 1 3 0 +1 1 2 0 +1 1 2 0 +1 1 2 0 +1 1 3 0 +1 3 0 +1 3 1 0 +1 3 1 0 +1 3 2 0 +1 3 2 0 +1 3 1 0 +1 3 1 0 +1 3 0 +1 3 0 +1 3 0 +1 3 1 0 +1 3 0 +1 3 1 0 +1 3 1 0 +1 3 0 +1 3 2 0 +1 3 2 0 +1 3 0 +1 3 0 +1 3 1 0 +0 +"1" "2" "3" "f:a77" diff --git a/include/Condorcet/Tests/src/Utils/VoteEntryParserTest.php b/include/Condorcet/Tests/src/Utils/VoteEntryParserTest.php new file mode 100644 index 0000000..13f5c30 --- /dev/null +++ b/include/Condorcet/Tests/src/Utils/VoteEntryParserTest.php @@ -0,0 +1,190 @@ +expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage($message); + + new VoteEntryParser($entry); + } + + public function voteBadNumericValueProvider(): iterable + { + yield [ + 'entry' => 'A>B ^g', + 'message' => "the value 'g' is not an integer.", + ]; + + yield [ + 'entry' => 'A>B ^a*b', + 'message' => "the value 'b' is not an integer.", + ]; + + yield [ + 'entry' => 'A>B*b', + 'message' => "the value 'b' is not an integer.", + ]; + + yield [ + 'entry' => 'A>B ^4Y ', + 'message' => "the value '4Y' is not an integer.", + ]; + } + + /** + * @dataProvider voteEntriesProvider() + */ + public function testVotesEntries(string $entry, array $expected): void + { + $parser = new VoteEntryParser($entry); + + self::assertSame($entry, $parser->originalEntry); + + self::assertSame($expected['comment'], $parser->comment); + self::assertSame($expected['multiple'], $parser->multiple); + self::assertSame($expected['ranking'], $parser->ranking); + self::assertSame($expected['tags'], $parser->tags); + self::assertSame($expected['weight'], $parser->weight); + } + + public function voteEntriesProvider(): iterable + { + yield [ + 'entry' => 'A >B = C>D', + 'expected' => [ + 'comment' => null, + 'multiple' => 1, + 'ranking' => [ + ['A'], + ['B', 'C'], + ['D'], + ], + 'tags' => null, + 'weight' => 1, + ], + ]; + + yield [ + 'entry' => 'tag1, tag2 || A >B = C>D ^ 7 * 42 # One Comment', + 'expected' => [ + 'comment' => 'One Comment', + 'multiple' => 42, + 'ranking' => [ + ['A'], + ['B', 'C'], + ['D'], + ], + 'tags' => ['tag1', 'tag2'], + 'weight' => 7, + ], + ]; + + yield [ + 'entry' => 'A >B = C>D *42#One Comment', + 'expected' => [ + 'comment' => 'One Comment', + 'multiple' => 42, + 'ranking' => [ + ['A'], + ['B', 'C'], + ['D'], + ], + 'tags' => null, + 'weight' => 1, + ], + ]; + + yield [ + 'entry' => 'A^ 7#', + 'expected' => [ + 'comment' => '', + 'multiple' => 1, + 'ranking' => [ + ['A'], + ], + 'tags' => null, + 'weight' => 7, + ], + ]; + + yield [ + 'entry' => ' ', + 'expected' => [ + 'comment' => null, + 'multiple' => 1, + 'ranking' => null, + 'tags' => null, + 'weight' => 1, + ], + ]; + + yield [ + 'entry' => ' tag1,tag2|| ', + 'expected' => [ + 'comment' => null, + 'multiple' => 1, + 'ranking' => null, + 'tags' => ['tag1', 'tag2'], + 'weight' => 1, + ], + ]; + + yield [ + 'entry' => '^7*4 ', + 'expected' => [ + 'comment' => null, + 'multiple' => 4, + 'ranking' => null, + 'tags' => null, + 'weight' => 7, + ], + ]; + + yield [ + 'entry' => ' #One Comment', + 'expected' => [ + 'comment' => 'One Comment', + 'multiple' => 1, + 'ranking' => null, + 'tags' => null, + 'weight' => 1, + ], + ]; + + yield [ + 'entry' => 'tag1,tag2||'.CondorcetElectionFormat::SPECIAL_KEYWORD_EMPTY_RANKING.'^7*42#FeteDuDindon', + 'expected' => [ + 'comment' => 'FeteDuDindon', + 'multiple' => 42, + 'ranking' => [], + 'tags' => ['tag1', 'tag2'], + 'weight' => 7, + ], + ]; + + yield [ + 'entry' => ' '.CondorcetElectionFormat::SPECIAL_KEYWORD_EMPTY_RANKING.' ', + 'expected' => [ + 'comment' => null, + 'multiple' => 1, + 'ranking' => [], + 'tags' => null, + 'weight' => 1, + ], + ]; + } +} diff --git a/include/Condorcet/Tests/src/Utils/VoteUtilTest.php b/include/Condorcet/Tests/src/Utils/VoteUtilTest.php new file mode 100644 index 0000000..ed98231 --- /dev/null +++ b/include/Condorcet/Tests/src/Utils/VoteUtilTest.php @@ -0,0 +1,77 @@ +expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: every tag must be of type string, array given'); + VoteUtil::tagsConvert(['not', 'a', 'string:', []]); + } + + public function testEmptyTagsThrowAnException(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: found empty tag'); + VoteUtil::tagsConvert('an , empty, tag , , in, the, middle'); + } + + /** + * @dataProvider tagsProvider() + */ + public function testTagsGetConverted($tags, $expected): void + { + $this->assertSame($expected, VoteUtil::tagsConvert($tags)); + } + + public function testGetRankingAsString(): void + { + // Empty ranking + $this->assertEquals('', VoteUtil::getRankingAsString([])); + + // String ranking + $this->assertEquals('A > B > C', VoteUtil::getRankingAsString(['A', 'B', 'C'])); + + // Array ranking + $this->assertEquals('A = B > C', VoteUtil::getRankingAsString([['A', 'B'], 'C'])); + + // Unsorted array ranking + $this->assertEquals('A = B > C', VoteUtil::getRankingAsString([['B', 'A'], 'C'])); + } + + public function tagsProvider(): iterable + { + yield 'null tags' => [ + 'tags' => null, + 'expected' => null, + ]; + + yield 'empty string' => [ + 'tags' => '', + 'expected' => null, + ]; + + yield 'empty array' => [ + 'tags' => [], + 'expected' => null, + ]; + + yield 'tags as string' => [ + 'tags' => 'these, are, some , tags', + 'expected' => ['these', 'are', 'some', 'tags'], + ]; + + yield 'tags as array' => [ + 'tags' => ['these', 'are', 'some', 'more', 'tags'], + 'expected' => ['these', 'are', 'some', 'more', 'tags'], + ]; + } +} diff --git a/include/Condorcet/Tests/src/VoteTest.php b/include/Condorcet/Tests/src/VoteTest.php new file mode 100644 index 0000000..ce54a1d --- /dev/null +++ b/include/Condorcet/Tests/src/VoteTest.php @@ -0,0 +1,814 @@ +election1 = new Election; + + $this->candidate1 = $this->election1->addCandidate('candidate1'); + $this->candidate2 = $this->election1->addCandidate('candidate2'); + $this->candidate3 = $this->election1->addCandidate('candidate3'); + $this->candidate4 = new Candidate('candidate4'); + $this->candidate5 = new Candidate('candidate5'); + $this->candidate6 = new Candidate('candidate6'); + } + + public function testTimestamp(): void + { + $vote1 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3]); + + self::assertEquals($vote1->getCreateTimestamp(), $vote1->getTimestamp()); + + $vote1->setRanking([$this->candidate1, $this->candidate2, $this->candidate3]); + + self::assertLessThan($vote1->getTimestamp(), $vote1->getCreateTimestamp()); + } + + public function testDifferentRanking(): never + { + $this->expectException(VoteNotLinkedException::class); + $this->expectExceptionMessage('The vote is not linked to an election'); + + // Ranking 1 + $vote1 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3]); + + $newRanking1 = $vote1->getRanking(); + + // Ranking 2 + self::assertTrue( + $vote1->setRanking( + [$this->candidate1, $this->candidate2, $this->candidate3] + ) + ); + + self::assertSame( + $newRanking1, + $vote1->getRanking() + ); + + // Ranking 3 + self::assertTrue( + $vote1->setRanking( + [4=> $this->candidate1, 6=> $this->candidate2, 14 => $this->candidate3] + ) + ); + + self::assertSame( + $newRanking1, + $vote1->getRanking() + ); + + // Add vote into an election + self::assertSame( + $this->election1->addVote($vote1), + $vote1 + ); + + // Ranking 4 + self::assertTrue( + $vote1->setRanking( + [$this->candidate1, $this->candidate2] + ) + ); + + self::assertSame( + $newRanking1, + $vote1->getContextualRanking($this->election1) + ); + + self::assertCount( + 2, + $vote1->getRanking() + ); + + // Ranking 5 + self::assertTrue( + $vote1->setRanking( + ['candidate1', 'candidate2'] + ) + ); + + self::assertSame( + $newRanking1, + $vote1->getContextualRanking($this->election1) + ); + + // Ranking 6 + self::assertTrue( + $vote1->setRanking( + [42 => 'candidate2', 142=> 'candidate1'] + ) + ); + + self::assertNotSame( + $newRanking1, + $vote1->getContextualRanking($this->election1) + ); + + // Ranking 7 + self::assertTrue( + $vote1->setRanking( + 'candidate1>Kim Jong>candidate2>Trump' + ) + ); + + self::assertSame( + $newRanking1, + $vote1->getContextualRanking($this->election1) + ); + + + // Ranking 8 + self::assertTrue( + $vote1->setRanking([ + 2=> $this->candidate2, + 1=> $this->candidate1, + 3=> $this->candidate3, + ]) + ); + + self::assertSame( + $newRanking1, + $vote1->getContextualRanking($this->election1) + ); + + + // Ranking 9 + + $vote = new Vote('candidate4 > candidate3 = candidate1 > candidate2'); + + self::assertSame( + CondorcetUtil::format($vote->getRanking()), + [ + 1 => 'candidate4', + 2 => ['candidate1', 'candidate3'], + 3 => 'candidate2', + ] + ); + + $election = new Election; + $election->parseCandidates('candidate2;candidate3;candidate4;candidate1'); + $election->addVote($vote); + + self::assertSame( + CondorcetUtil::format($vote->getContextualRanking($election)), + [ + 1 => 'candidate4', + 2 => ['candidate1', 'candidate3'], + 3 => 'candidate2', + ] + ); + + self::assertSame( + $election->getResult()->getResultAsArray(true), + [ + 1 => 'candidate4', + 2 => ['candidate1', 'candidate3'], + 3 => 'candidate2', + ] + ); + + // Contextual Ranking Fail + + $unexpectedElection = new Election; + + $vote1->getContextualRanking($unexpectedElection); + } + + public function testSimpleRanking(): void + { + // Ranking 1 + $vote1 = new Vote('candidate1 > candidate3 = candidate2 > candidate4'); + + self::assertSame($vote1->getSimpleRanking(), 'candidate1 > candidate2 = candidate3 > candidate4'); + + $this->election1->addVote($vote1); + + self::assertSame($vote1->getSimpleRanking($this->election1), 'candidate1 > candidate2 = candidate3'); + } + + public function testProvisionalCandidateObject(): void + { + // Ranking 1 + $vote1 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3]); + $newRanking1 = $vote1->getRanking(); + $this->election1->addVote($vote1); + + // I + self::assertTrue( + $vote1->setRanking([ + new Candidate('candidate1'), + $this->candidate2, + $this->candidate3, + ]) + ); + + self::assertNotSame( + $newRanking1, + $vote1->getContextualRanking($this->election1) + ); + + self::assertSame( + [1 => [$this->candidate2], + 2 => [$this->candidate3], + 3 => [$this->candidate1], ], + $vote1->getContextualRanking($this->election1) + ); + + self::assertSame( + [1 => 'candidate2', + 2 => 'candidate3', + 3 => 'candidate1', ], + $vote1->getContextualRankingAsString($this->election1) + ); + + // II + $vote2 = new Vote('candidate1>candidate2'); + + self::assertTrue($vote2->getRanking()[1][0]->getProvisionalState()); + $vote2_firstRanking = $vote2->getRanking(); + + $this->election1->addVote($vote2); + + self::assertFalse($vote2->getRanking()[1][0]->getProvisionalState()); + + self::assertSame( + [1 => [$this->candidate1], + 2 => [$this->candidate2], + 3 => [$this->candidate3], ], + $vote2->getContextualRanking($this->election1) + ); + + self::assertNotEquals( + $vote2_firstRanking, + $vote2->getRanking() + ); + + + // III + $otherCandidate1 = new candidate('candidate1'); + $otherCandidate2 = new candidate('candidate2'); + + $vote3 = new Vote([$otherCandidate1, $otherCandidate2, $this->candidate3]); + + self::assertFalse($vote3->getRanking()[1][0]->getProvisionalState()); + $vote3_firstRanking = $vote3->getRanking(); + + $this->election1->addVote($vote3); + + self::assertFalse($vote2->getRanking()[1][0]->getProvisionalState()); + + self::assertSame( + [1 => [$this->candidate3], + 2 => [$this->candidate1, $this->candidate2], ], + $vote3->getContextualRanking($this->election1) + ); + + self::assertSame( + [1 => 'candidate3', + 2 => ['candidate1', 'candidate2'], ], + $vote3->getContextualRankingAsString($this->election1) + ); + + self::assertEquals( + $vote3_firstRanking, + $vote3->getRanking() + ); + } + + public function testDifferentElection(): void + { + $election1 = $this->election1; + + $election2 = new Election; + $election2->addCandidate($this->candidate1); + $election2->addCandidate($this->candidate2); + $election2->addCandidate($this->candidate4); + + $vote1 = new Vote([ + $this->candidate1, + $this->candidate2, + $this->candidate3, + $this->candidate4, + ]); + $vote1_originalRanking = $vote1->getRanking(); + + $election1->addVote($vote1); + $election2->addVote($vote1); + + self::assertSame($vote1_originalRanking, $vote1->getRanking()); + self::assertSame( + [1=>[$this->candidate1], 2=>[$this->candidate2], 3=>[$this->candidate3]], + $vote1->getContextualRanking($election1) + ); + self::assertSame( + [1=>[$this->candidate1], 2=>[$this->candidate2], 3=>[$this->candidate4]], + $vote1->getContextualRanking($election2) + ); + self::assertNotSame($vote1->getRanking(), $vote1->getContextualRanking($election2)); + + self::assertTrue($vote1->setRanking([ + [$this->candidate5, $this->candidate2], + $this->candidate3, + ])); + + self::assertSame( + [1=>[$this->candidate2], 2=>[$this->candidate3], 3=>[$this->candidate1]], + $vote1->getContextualRanking($election1) + ); + self::assertSame( + [1=>[$this->candidate2], 2=>[$this->candidate1, $this->candidate4]], + $vote1->getContextualRanking($election2) + ); + } + + public function testValidTags(): void + { + $vote1 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3]); + + $targetTags = ['tag1', 'tag2', 'tag3']; + + self::assertTrue($vote1->addTags( + 'tag1,tag2,tag3' + )); + + self::assertSame( + $targetTags, + array_values($vote1->getTags()) + ); + + self::assertTrue($vote1->removeAllTags()); + self::assertSame( + [], + $vote1->getTags() + ); + + self::assertTrue($vote1->addTags( + ['tag1', 'tag2', 'tag3'] + )); + + self::assertSame( + $targetTags, + array_values($vote1->getTags()) + ); + + self::assertEquals(['tag2'], $vote1->removeTags('tag2')); + + self::assertEquals( + ['tag1', 'tag3'], + array_values($vote1->getTags()) + ); + + self::assertTrue($vote1->removeAllTags()); + + self::assertSame( + [], + $vote1->getTags() + ); + } + + public function testBadTagInput1(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: every tag must be of type string, integer given'); + + $vote = new Vote('A'); + $vote->addTags(['tag1', 42]); + } + + public function testBadTagInput2(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: found empty tag'); + + $vote = new Vote('A'); + $vote->addTags( + ['tag1 ', ' tag2', ' tag3 ', ' '] + ); + + self::assertSame( + [], + $vote->getTags() + ); + + self::assertTrue($vote->removeAllTags()); + } + + public function testBadTagInput3(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: found empty tag'); + + $vote = new Vote('A'); + $vote->addTags( + ' tag1,tag2 , tag3 ,' + ); + + self::assertSame( + [], + $vote->getTags() + ); + + self::assertTrue($vote->removeAllTags()); + } + + public function testBadTagInput4(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: every tag must be of type string, NULL given'); + + $vote = new Vote('A'); + $vote->addTags( + [null] + ); + + self::assertSame( + [], + $vote->getTags() + ); + } + + public function testBadTagInput5(): void + { + $vote = new Vote('A'); + $vote->addTags( + [] + ); + + self::assertSame( + [], + $vote->getTags() + ); + } + + public function testAddRemoveTags(): void + { + $vote1 = new Vote([$this->candidate1, $this->candidate2, $this->candidate3]); + + $vote1->addTags('tag1'); + $vote1->addTags(['tag2', 'tag3']); + self::assertTrue($vote1->addTags('tag4,tag5')); + + self::assertSame( + ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'], + $vote1->getTags() + ); + + self::assertsame([], $vote1->removeTags('')); + $vote1->removeTags('tag1'); + $vote1->removeTags(['tag2', 'tag3']); + self::assertsame($vote1->removeTags('tag4,tag5,tag42'), ['tag4', 'tag5']); + + self::assertSame( + [], + $vote1->getTags() + ); + + self::assertTrue($vote1->addTags('tag4,tag5')); + self::assertTrue($vote1->removeAllTags()); + + self::assertSame( + [], + $vote1->getTags() + ); + } + + public function testTagsOnConstructorByStringInput(): void + { + $vote1 = new Vote('tag1,tag2 ||A > B >C', 'tag3,tag4'); + + self::assertSame(['tag3', 'tag4', 'tag1', 'tag2'], $vote1->getTags()); + + self::assertSame('A > B > C', $vote1->getSimpleRanking()); + + $vote2 = new Vote((string) $vote1); + + self::assertSame((string) $vote1, (string) $vote2); + } + + public function testCloneVote(): void + { + // Ranking 1 + $vote1 = new Vote('candidate1 > candidate3 = candidate2 > candidate4'); + + $this->election1->addVote($vote1); + + $vote2 = clone $vote1; + + self::assertSame(0, $vote2->countLinks()); + self::assertSame(1, $vote1->countLinks()); + } + + public function testIterator(): void + { + $vote = new Vote('C > B > A'); + + foreach ($vote as $key => $value) { + self::assertSame($vote->getRanking()[$key], $value); + } + } + + public function testWeight(): never + { + $vote = new Vote('A>B>C^42'); + + self::assertsame(42, $vote->getWeight()); + self::assertsame(2, $vote->setWeight(2)); + self::assertsame(2, $vote->getWeight()); + self::assertsame(1, $vote->getWeight($this->election1)); + + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage("The format of the vote is invalid: the value 'a' is not an integer."); + + $vote = new Vote('A>B>C^a'); + } + + public function testCustomTimestamp(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: Timestamp format of vote is not correct'); + + $vote = new Vote( + 'A>B>C', + null, + $createTimestamp = microtime(true) - (3600 * 1000) + ); + + self::assertSame($createTimestamp, $vote->getTimestamp()); + + $vote->setRanking('B>C>A', $ranking2Timestamp = microtime(true) - (60 * 1000)); + + self::assertSame($ranking2Timestamp, $vote->getTimestamp()); + + self::assertSame($createTimestamp, $vote->getCreateTimestamp()); + + self::assertSame($createTimestamp, $vote->getHistory()[0]['timestamp']); + + self::assertSame($ranking2Timestamp, $vote->getHistory()[1]['timestamp']); + + $vote->setRanking('A', 1); + } + + public function testHashCode(): void + { + $vote = new Vote('A>B>C'); + + $hashCode[1] = $vote->getHashCode(); + + $vote->addTags('tag1'); + + $hashCode[2] = $vote->getHashCode(); + + $vote->setRanking('C>A>B'); + + $hashCode[3] = $vote->getHashCode(); + + $vote->setRanking('C>A>B'); + + $hashCode[4] = $vote->getHashCode(); + + self::assertNotsame($hashCode[2], $hashCode[1]); + self::assertNotsame($hashCode[3], $hashCode[2]); + self::assertNotSame($hashCode[4], $hashCode[3]); + } + + public function testCountRankingCandidates(): void + { + $vote = new Vote('A>B>C'); + + self::assertsame(3, $vote->countRankingCandidates()); + } + + public function testInvalidWeight(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid: the vote weight can not be less than 1'); + + $vote = new Vote('A>B>C'); + + $vote->setWeight(0); + } + + public function testInvalidTag1(): never + { + $this->expectException(\TypeError::class); + + $vote = new Vote('A>B>C'); + + $vote->addTags(true); + } + + public function testInvalidTag2(): never + { + $this->expectException(\TypeError::class); + + $vote = new Vote('A>B>C'); + + $vote->addTags(42); + } + + public function testRemoveCandidate(): never + { + $vote1 = new Vote('candidate1 > candidate2 > candidate3 ^ 42'); + + $this->election1->addVote($vote1); + + self::assertSame('candidate1 > candidate2 > candidate3', $this->election1->getResult()->getResultAsString()); + + $vote1->removeCandidate('candidate2'); + + self::assertSame('candidate1 > candidate3 ^42', $vote1->getSimpleRanking()); + + self::assertSame('candidate1 > candidate3 > candidate2', $this->election1->getResult()->getResultAsString()); + + $vote1->removeCandidate($this->candidate3); + + self::assertSame('candidate1 > candidate2 = candidate3', $this->election1->getResult()->getResultAsString()); + + $this->expectException(CandidateDoesNotExistException::class); + $this->expectExceptionMessage('This candidate does not exist: candidate4'); + + $vote1->removeCandidate($this->candidate4); + } + + public function testRemoveCandidateInvalidInput(): never + { + $vote1 = new Vote('candidate1 > candidate2 > candidate3 ^ 42'); + + $this->expectException(\TypeError::class); + + $vote1->removeCandidate([]); + } + + public function testVoteHistory(): void + { + $this->election1->addCandidate($this->candidate4); + $this->election1->addCandidate($this->candidate5); + $this->election1->addCandidate($this->candidate6); + + + $vote1 = $this->election1->addVote(['candidate1', 'candidate2']); + + self::assertCount(1, $vote1->getHistory()); + + // ------- + + $vote2 = $this->election1->addVote('candidate1 > candidate2'); + + self::assertCount(1, $vote2->getHistory()); + + // ------- + + $vote3 = new Vote(['candidate1', 'candidate2']); + + $this->election1->addVote($vote3); + + self::assertCount(2, $vote3); + + // ------- + + $this->election1->parseVotes('voterParsed || candidate1 > candidate2'); + + $votes_lists = $this->election1->getVotesListGenerator('voterParsed', true); + $vote4 = $votes_lists->current(); + + self::assertCount(1, $vote4->getHistory()); + + // ------- + + $vote5 = new Vote([$this->candidate5, $this->candidate6]); + + $this->election1->addVote($vote5); + + self::assertCount(1, $vote5->getHistory()); + + // ------- + + $vote6 = new Vote([$this->candidate5, 'candidate6']); + + $this->election1->addVote($vote6); + + self::assertCount(2, $vote6->getHistory()); + + // ------- + + $vote7 = new Vote([$this->candidate6, 'candidate8']); + + $candidate8 = $vote7->getAllCandidates()[1]; + + self::assertsame('candidate8', $candidate8->getName()); + + self::assertTrue($candidate8->getProvisionalState()); + + $this->election1->addVote($vote7); + + self::assertTrue($candidate8->getProvisionalState()); + + self::assertCount(1, $vote7->getHistory()); + } + + public function testBadRankingInput1(): never + { + $this->expectException(\TypeError::class); + + $vote = new Vote(42); + } + + public function testBadRankingInput2(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid'); + + $candidate = new Candidate('A'); + + $vote = new Vote([$candidate, $candidate]); + } + + public function testEmptyVoteContextualInRanking(): void + { + $vote = $this->election1->addVote('candidate4 > candidate5'); + + self::assertSame( + [1 => [$this->candidate1, $this->candidate2, $this->candidate3]], + $vote->getContextualRanking($this->election1) + ); + + $cr = $vote->getContextualRankingAsString($this->election1); + + self::assertSame( + [1 => ['candidate1', 'candidate2', 'candidate3']], + $cr + ); + } + + public function testNonEmptyVoteContextualInRanking(): void + { + $vote = $this->election1->addVote('candidate1 = candidate2 = candidate3'); + + self::assertSame( + [1 => [$this->candidate1, $this->candidate2, $this->candidate3]], + $vote->getContextualRanking($this->election1) + ); + + $cr = $vote->getContextualRankingAsString($this->election1); + + self::assertSame( + [1 => ['candidate1', 'candidate2', 'candidate3']], + $cr + ); + } + + + // https://github.com/julien-boudry/Condorcet/issues/32 + public function testDuplicateCandidates1(): never + { + $this->expectException(VoteInvalidFormatException::class); + $this->expectExceptionMessage('The format of the vote is invalid'); + + new Vote('Spain>Japan>France>Netherlands>Australia>France'); + } + + + // https://github.com/julien-boudry/Condorcet/issues/32 + public function testDuplicateCandidates2(): void + { + $election = new Election; + $election->parseCandidates('Spain;Japan;France;Netherlands;Australia'); + + $vote = $election->addVote('Spain>Japan>France>Netherlands>Australia>france'); + + self::assertSame( + 'Spain > Japan > France > Netherlands > Australia', + $vote->getSimpleRanking($election) + ); + } + + public function testEmptySpecialKeyWord(): void + { + $vote1 = new Vote(CondorcetElectionFormat::SPECIAL_KEYWORD_EMPTY_RANKING); + $vote2 = new Vote(' '.CondorcetElectionFormat::SPECIAL_KEYWORD_EMPTY_RANKING.' '); + + self::assertSame([], $vote1->getRanking()); + self::assertSame([], $vote2->getRanking()); + } +} diff --git a/include/Condorcet/VOTING_METHODS.md b/include/Condorcet/VOTING_METHODS.md new file mode 100644 index 0000000..426527b --- /dev/null +++ b/include/Condorcet/VOTING_METHODS.md @@ -0,0 +1,785 @@ +> **[Presentation](README.md) | [Manual](https://github.com/julien-boudry/Condorcet/wiki) | [Methods References](Documentation/README.md) | [Tests](Tests/)** + +Condorcet PHP: Voting Methods +=========================== +* **[Implementation philosophy](#implementation-philophy)** + +> _See also **[the manual](https://github.com/julien-boudry/Condorcet/wiki/I-%23-Installation---Basic-Configuration-%23-2.-Voting-Methods-%28library%29)**_ + +# Natively implemented methods +*The modular architecture allows you to import new methods as external classes. These are preloaded into the distribution.* + +## Single winner methods +_Designed for electing a single winner. But return a full ranking._ + +* **Condorcet Basic** Give you the natural winner or loser of Condorcet if there is one. +* **Borda count** + * **[Borda System](#borda-count)** + * **[Dowdall system (Nauru)](#dowdall-system-nauru)** +* **[Copeland](#copeland)** +* **Dodgson Approximations** + * **[Dodgson Quick](#dodgson-quick)** + * **[Dodgson Tideman approximation](#dodgson-tideman-approximation)** +* **[Instant-runoff](#instant-runoff-alternative-vote)** *(Alternative Vote / Preferential Voting)* +* **[Kemeny–Young](#kemenyyoung)** +* **Majority Family** + * **[First-past-the-post](#first-past-the-post)** + * **[Multiple Rounds system](#multiple-rounds-system)** +* **Minimax Family** + * **[Minimax Winning](#minimax-winning)** + * **[Minimax Margin](#minimax-margin)** + * **[Minimax Opposition](#minimax-opposition)** +* **Ranked Pairs Family** *(Tideman method)* + * **[Ranked Pairs Margin](#ranked-pairs-margin)** + * **[Ranked Pairs Winning](#ranked-pairs-winning)** +* **Schulze Method** + * **[Schulze Winning](#schulze-winning)** *(recommended)* + * **[Schulze Margin](#schulze-margin)** + * **[Schulze Ratio](#schulze-ratio)** + +## Proportional methods +_Designed for electing an assembly. Return a ranking of elected candidates._ + +* **STV Family** + * **[Single Transferable Vote](#single-transferable-vote)** *(The classical STV method)* + * **[CPO-STV](#cpo-stv)** *Comparison of Pairs of Outcomes by the Single Transferable Vote, by Nicolaus Tideman* +* **Highest Average / Largest Remainder Family** + * **[Highest Average: Sainte-Laguë](#sainte-laguë--webster-method)** *Include Variants like Norway ou Sweden methods* + * **[Highest Average: Thomas Jefferson / D'Hondt](#jefferson--dhondt-method)** + * **[Largest Remainder: Hare-LR / Droop-LR / Imperiali-LR / Hagenbach-Bischoff-LR](#hare-lr--droop-lr--imperiali-lr--hagenbach-bischoff-lr)** + + + +--------------------------------------- + +# Implementation Philophy + +### Result tie-breaking +Unless explicitly stated otherwise in the details below, no tie-breaking is added to methods, we kept them pure. +The results are therefore likely to contain ties in some ranks. Which according to the algorithms is more or less frequent, but always tends to become less likely in proportion to the size of the election. + +### Tie into a vote rank +Unless you have prohibited ties yourself or via a filter (CondorcetPHP >= 1.8), the votes are therefore likely to contain ties on certain ranks. In principle, this does not particularly disturb Condorcet's methods, since they are based on the Pairwise. +This is more annoying for other methods like Borda, Instant-runoff or Ftpt. These methods being based on the rank assigned. How each handles these cases is specified below. Keep in mind that it can vary depending on the implementations. Some choices had to be made for each of them. + +### Implicit vs Explicit Ranking +Please read the manual [about explicit and implicit ranking](https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-3.-Ranking-mode---Implicit-versus-Partial) modes. +In terms of implementation, what you have to understand is that algorithms and pairwise are blind. And see votes in their implicit or explicit context, which can significantly change the results of some of them. + +--------------------------------------- + +# Single Winner methods - Details & Implementation +## Condorcet Basic + +> **Family:** Condorcet +> **Variant used:** *None* +> **Wikipedia:** https://en.wikipedia.org/wiki/Condorcet_method + +### Implementation Comments +*None* + +```php +// Will return the strict natural Condorcet Winner candidate. Or Null if there is not. +$election->getCondorcetWinner() ; +// Will return the strict natural Condorcet Loser candidate. Or Null if there is not. +$election->getCondorcetLoser() ; +``` + + +## Borda Count + +> **Family:** Borda Count +> **Variant used:** *Starting at 1* +> **Wikipedia:** https://en.wikipedia.org/wiki/Borda_count +> *** +> **Methods alias available (for function call)**: "BordaCount","Borda Count","Borda","Méthode Borda" + +### Implementation Comments +By default the option is to start the count at n - 1. You can change it with BordaCount::setOption(), see below. + +In case of tie into a vote rank, follow this example: +``` +A>B=C=D=E>F +A: 6 points +B/C/D/E: (5+4+3+2) / 4 = 3.5 points each +F: 1 point +``` + +In case of explicit voting is disabled. Missing rank does not earn points, but the existing rank are not penalized. + +### Code example +```php +// Get Full Ranking +$election->getResult('BordaCount') ; + +// Just get Winner or Loser +$election->getWinner('BordaCount') ; +$election->getLoser('BordaCount') ; + +// Get Stats +$election->getResult('BordaCount')->getStats() ; + +// Chante the staring point to n - 0 +$election->setMethodOption('BordaCount', 'Starting', 0) ; +$election->getResult('BordaCount') ; +``` + + +## Dowdall system (Nauru) + +> **Family:** Borda Count +> **Variant used:** *Dowdall System* +> **Wikipedia:** https://en.wikipedia.org/wiki/Borda_count +> *** +> **Methods alias available (for function call)**: "DowdallSystem","Dowdall System","Nauru", "Borda Nauru" + +### Implementation Comments + *See comments on the original Borda method above.* + +### Code example +```php +// Get Full Ranking +$election->getResult('DowdallSystem') ; + +// Just get Winner or Loser +$election->getWinner('DowdallSystem') ; +$election->getLoser('DowdallSystem') ; + +// Get Stats +$election->getResult('DowdallSystem')->getStats() ; +``` + + +## Copeland + +> **Family:** Copeland method +> **Variant used:** *None* +> **Wikipedia:** http://en.wikipedia.org/wiki/Copeland%27s_method +> *** +> **Methods alias available (for function call)**: "Copeland" + +### Implementation Comments + *None* + +### Code example + +```php +// Get Full Ranking +$election->getResult('Copeland') ; + +// Just get Winner or Loser +$election->getWinner('Copeland') ; +$election->getLoser('Copeland') ; + +// Get Stats +$election->getResult('Copeland')->getStats() ; +``` + + +## Dodgson Quick + +> **Family:** Dodgson method +> **Variant used:** Approximation for Dodgson method called "Dodgson Quick" from https://www.maa.org/sites/default/files/pdf/cmj_ftp/CMJ/September%202010/3%20Articles/6%2009-229%20Ratliff/Dodgson_CMJ_Final.pdf +> **Wikipedia:** https://en.wikipedia.org/wiki/Dodgson%27s_method +> *** +> **Methods alias available (for function call)**: "Dodgson Quick" / "DodgsonQuick" / "Dodgson Quick Winner" + +### Implementation Comments + *None* + +### Code example +```php +// Get Full Ranking +$election->getResult('Dodgson Quick') ; + +// Just get Winner or Loser +$election->getWinner('Dodgson Quick') ; +$election->getLoser('Dodgson Quick') ; + +// Get Stats +$election->getResult('Dodgson Quick')->getStats() ; +``` + + +## Dodgson Tideman Approximation + +> **Family:** Dodgson method +> **Variant used:** Approximation for Dodgson method called "Tideman approximation" from _[Lewis Carroll, voting, and the taxicab metric](https://www.maa.org/sites/default/files/pdf/cmj_ftp/CMJ/September%202010/3%20Articles/6%2009-229%20Ratliff/Dodgson_CMJ_Final.pdf)_ +> **Wikipedia:** https://en.wikipedia.org/wiki/Dodgson%27s_method +> *** +> **Methods alias available (for function call)**: "Dodgson Tideman Approximation" / "DodgsonTidemanApproximation" / "Dodgson Tideman" / "DodgsonTideman" + +### Implementation Comments + *None* + +### Code example +```php +// Get Full Ranking +$election->getResult('Dodgson Tideman') ; + +// Just get Winner or Loser +$election->getWinner('Dodgson Tideman') ; +$election->getLoser('Dodgson Tideman') ; + +// Get Stats +$election->getResult('Dodgson Tideman')->getStats() ; +``` + + +## Instant-runoff (Alternative Vote) + +> **Family:** Instant-runoff +> **Variant used:** *None* +> **Wikipedia:** https://en.wikipedia.org/wiki/Instant-runoff_voting +> *** +> **Methods alias available (for function call)**: "Instant-runoff", "InstantRunoff", "IRV", "preferential voting", "ranked-choice voting", "alternative vote", "AlternativeVote", "transferable vote", "Vote alternatif" + +### Implementation Comments +In case of tie into a vote rank, rank is ignored like he never existed. + +An additional tie-breaking tentative is added in case of tie into the preliminary result set. First, comparing candidate pairwise, in a second attempt compare the total number of pairwise wins (global context), and in a third desperate attempt, compare the balance of their victory/defeat in a global Pairwise context. + +### Code example + +```php +// Get Full Ranking +$election->getResult('Instant-runoff') ; + +// Just get Winner or Loser +$election->getWinner('Instant-runoff') ; +$election->getLoser('Instant-runoff') ; + +// Get Stats +$election->getResult('Instant-runoff')->getStats() ; +``` + + +## Kemeny–Young + +> **Family:** Kemeny–Young method +> **Variant used:** *None* +> **Wikipedia:** http://en.wikipedia.org/wiki/Kemeny-Young_method _Kemeny-Young +> *** +> **Methods alias available (for function call)**: "Kemeny–Young" / "Kemeny-Young" / "Kemeny Young" / "KemenyYoung" / "Kemeny rule" / "VoteFair popularity ranking" / "Maximum Likelihood Method" / "Median Relation" + +### Implementation Comments +Kemeny-Young is currently limited up to 10 candidates. It is very fast up to 9. At 10, this should remain under 30 seconds of processing even under a very modest system. Beyond that, it is certainly playable at least up to 12, but with a much higher processing time, but a constantly low memory. But you must not ask for the `FULL` stats verbosity. + +### Code example +```php +// Get Full Ranking +$election->getResult('Kemeny-Young') ; + +// Just get Winner or Loser +$election->getWinner('Kemeny-Young'') ; +$election->getLoser('Kemeny-Young') ; + +// Get Stats +$election->getResult('Kemeny-Young')->getStats() ; + +// Get all the stats (can slow down and use a lot of memory starting 9 candidates) +$election->->setStatsVerbosity(StatsVerbosity::FULL); +$election->getResult('Kemeny-Young')->getStats() ; +``` + + +## First-past-the-post + +> **Family:** Majority +> **Variant used:** *See implementation comment* +> **Wikipedia:** https://en.wikipedia.org/wiki/First-past-the-post_voting +> *** +> **Methods alias available (for function call)**: "First-past-the-post voting", "First-past-the-post", "First Choice", "FirstChoice", "FPTP", "FPP", "SMP" + +### Implementation Comments +In case of tie into the first rank. All non-commissioned candidates earn points, but only a fraction. But not 1 point, the result of this computation: 1/(candidate-in-rank). + +For example: ```A = B > C``` +A/B earn each 0.5 points + +### Code example +```php +// Get Full Ranking +$election->getResult('FPTP') ; + +// Just get Winner or Loser +$election->getWinner('FPTP') ; +$election->getLoser('FPTP') ; + +// Get Stats +$election->getResult('FPTP')->getStats() ; +``` + + +## Multiple Rounds system + +> **Family:** Majority +> **Variant used:** *See implementation comment* +> **Wikipedia:** https://en.wikipedia.org/wiki/Two-round_system +> *** +> **Methods alias available (for function call)**: "Multiple Rounds System", "MultipleRoundsSystem", "Multiple Rounds", "Majority", "Majority System", "Two-round system", "second ballot", "runoff voting", "ballotage", "two round system", "two round", "two rounds", "two rounds system", "runoff voting" + +### Implementation Comments +In case of tie into the first rank. All non-commissioned candidates earn points, but only a fraction. But not 1 point, the result of this computation: 1/(candidate-in-rank). +For example: ```A = B > C``` +A/B earn each 0.5 points + +Method is trying to keep only two candidates for the next round. But that may be more in the event of a perfect tie. + +### Code example +```php +// Get Full Ranking +$election->getResult('Multiple Rounds System') ; + +// Just get Winner or Loser +$election->getWinner('Multiple Rounds System') ; +$election->getLoser('Multiple Rounds System') ; + +// Get Stats +$election->getResult('Multiple Rounds System')->getStats() ; +``` + + +## Minimax Winning + +> **Family:** Minimax method +> **Variant used:** Winning *(Does not satisfy the Condorcet loser criterion)* +> **Wikipedia:** https://en.wikipedia.org/wiki/Minimax_Condorcet +> *** +> **Methods alias available (for function call)**: "Minimax Winning" / "MinimaxWinning" / "Minimax" / "Minimax_Winning" / "Simpson" / "Simpson-Kramer" / "Simpson-Kramer Method" / "Simpson Method" + +### Implementation Comments + *None* + +### Code example +```php +// Get Full Ranking +$election->getResult('Minimax Winning') ; + +// Just get Winner or Loser +$election->getWinner('Minimax Winning') ; +$election->getLoser('Minimax Winning') ; + +// Get Stats +$election->getResult('Minimax Winning')->getStats() ; +``` + + +## Minimax Margin + +> **Family:** Minimax method +> **Variant used:** Margin *(Does not satisfy the Condorcet loser criterion)* +> **Wikipedia:** https://en.wikipedia.org/wiki/Minimax_Condorcet +> *** +> **Methods alias available (for function call)**: "Minimax Margin" / "MinimaxMargin" / "MinimaxMargin" / "Minimax_Margin" + +### Implementation Comments + *None* + +### Code example +```php +// Get Full Ranking +$election->getResult('Minimax Margin') ; + +// Just get Winner or Loser +$election->getWinner('Minimax Margin') ; +$election->getLoser('Minimax Margin') ; + +// Get Stats +$election->getResult('Minimax Margin')->getStats() ; +``` + + +## Minimax Opposition + +> **Family:** Minimax method +> **Variant used:** Opposition *(By nature, this alternative does not meet any criterion of Condorcet)* +> **Wikipedia:** https://en.wikipedia.org/wiki/Minimax_Condorcet +> *** +> **Methods alias available (for function call)**: "Minimax Opposition" / "MinimaxOpposition" / "Minimax_Opposition" + +### Implementation Comments + *None* + +### Code example +```php +// Get Full Ranking +$election->getResult('Minimax Opposition') ; + +// Just get Winner or Loser +$election->getWinner('Minimax Opposition') ; +$election->getLoser('Minimax Opposition') ; + +// Get Stats +$election->getResult('Minimax Opposition')->getStats() ; +``` + + +## Ranked Pairs Margin + +> **Family:** Ranked Pairs +> **Variant used:** Margin *(Ranked Pairs Margin is used by Nicolaus Tideman himself from originals papers. But it's not necessarily the most common. Most other documentation preferring the Winning variant. Even Wikipedia is the different from one language to another.)* +**Wikipedia:** https://en.wikipedia.org/wiki/Ranked_pairs +> *** +> **Methods alias available (for function call)**: "Ranked Pairs Margin" / "Tideman Margin" / "RP Margin" / "Ranked Pairs" / "RankedPairs" / "Tideman method" + +### Implementation Comments +In the event of the impossibility of ordering a pair by their margin of victory. Try to separate them when possible by their smaller minority opposition. +In case of a tie in the ranking result. No advanced methods are used. It is, therefore, an implementation following the first paper published in 1987. Markus Schulze advice a tie-breaking method, but it brings unnecessary complexity and is partly based on randomness. this method can, therefore, come out ties on some ranks. Even if that is very unlikely on an honest election of good size. + +### Code example +```php +// Get Full Ranking +$election->getResult('Ranked Pairs Margin') ; + +// Just get Winner or Loser +$election->getWinner('Ranked Pairs Margin') ; +$election->getLoser('Ranked Pairs Margin') ; + +// Get Stats +$election->getResult('Ranked Pairs Margin')->getStats() ; +``` + + +## Ranked Pairs Winning + +> **Family:** Ranked Pairs +> **Variant used:** Winning +> **Wikipedia:** https://en.wikipedia.org/wiki/Ranked_pairs +> *** +> **Methods alias available (for function call)**: "Ranked Pairs Winning" / "Tideman Winning" / "RP Winning" + +### Implementation Comments +In the event of the impossibility of ordering a pair by their margin of victory. Try to separate them when possible by their smaller minority opposition. +In case of a tie in the ranking result. No advanced methods are used. It is, therefore, an implementation following the first paper published in 1987. Markus Schulze advice a tie-breaking method, but it brings unnecessary complexity and is partly based on randomness. this method can, therefore, come out ties on some ranks. Even if that is very unlikely on an honest election of good size. + +### Code example +```php +// Get Full Ranking +$election->getResult('Ranked Pairs Winning') ; + +// Just get Winner or Loser +$election->getWinner('Ranked Pairs Winning') ; +$election->getLoser('Ranked Pairs Winning') ; + +// Get Stats +$election->getResult('Ranked Pairs Winning')->getStats() ; +``` + + +## Schulze Winning + +> **Family:** Schulze method +> **Variant used:** Winning *(Schulze Winning is recommended by Markus Schulze himself. This is the default choice. This variant is also known as Schulze Method.)* +> **Wikipedia:** https://en.wikipedia.org/wiki/Schulze_method +> *** +> **Methods alias available (for function call)**: "Schulze Winning" / "Schulze" / "SchulzeWinning" / "Schulze_Winning" / "Schwartz Sequential Dropping" / "SSD" / "Cloneproof Schwartz Sequential Dropping" / "CSSD" / "Beatpath" / "Beatpath Method" / "Beatpath Winner" / "Path Voting" / "Path Winner" + +### Implementation Comments + *None* + +### Code example +```php +// Get Full Ranking +$election->getResult('Schulze') ; + +// Just get Winner or Loser +$election->getWinner('Schulze') ; +$election->getLoser('Schulze') ; + +// Get Stats +$election->getResult('Schulze')->getStats() ; +``` + + +## Schulze Margin + +> **Family:** Schulze method +> **Variant used:** Margin +> **Wikipedia:** https://en.wikipedia.org/wiki/Schulze_method +> *** +> **Methods alias available (for function call)**: "Schulze Margin" / "SchulzeMargin" / "Schulze_Margin" + +### Implementation Comments + *None* + +### Code example +```php +// Get Full Ranking +$election->getResult('Schulze Margin') ; + +// Just get Winner or Loser +$election->getWinner('Schulze Margin') ; +$election->getLoser('Schulze Margin') ; + +// Get Stats +$election->getResult('Schulze Margin')->getStats() ; +``` + + +## Schulze Ratio + +> **Family:** Schulze method +> **Variant used:** Ratio +> **Wikipedia:** https://en.wikipedia.org/wiki/Schulze_method +> *** +> **Methods alias available (for function call)**: "Schulze Ratio" / "SchulzeRatio" / "Schulze_Ratio" + +### Implementation Comments +The original specification is incomplete. She says to compute the ratio as follow: +```$candidateA_versus_CandidateB['pairwise_win'] / $candidateA_versus_CandidateB ['pairwise_lose'] = Ratio``` +We don't know how to manage division by zero when it's happened, which is very unlikely on large elections but can happen. Actually, but it can change to a better solution, we add 1 on left and right, only in this case. + +### Code example +```php +// Get Full Ranking +$election->getResult('Schulze Ratio') ; + +// Just get Winner or Loser +$election->getWinner('Schulze Ratio') ; +$election->getLoser('Schulze Ratio') ; + +// Get Stats +$election->getResult('Schulze Ratio')->getStats() ; +``` + + +# Proportional Methods - Details & Implementation + +## Single Transferable Vote (family) +### Single Transferable Vote + +> **Family:** Single Transferable Vote +> **Proportional type:** Individual _(A candidate can be elected only once (1 seat). Ranking will never return many time the same candidate.)_ +> **Default STV Quota:** Droop +> **Variant used:** *None* +> **Wikipedia:** https://en.wikipedia.org/wiki/Single_transferable_vote +> *** +> **Methods alias available (for function call)**: "STV" / "Single Transferable Vote" / "SingleTransferableVote" + +#### Implementation Comments +###### Fundamentals +- In case of tie into a vote rank, rank is ignored like he never existed. It's recommended to use the native vote constraint `NoTie` if you are not sure of your inputs: `$election->addConstraint(NoTie::class)`. +- The implementation of this method does not support parties. A candidate is elected only once, whatever the number of seats. +- Non-elected candidates are not included in the ranking. The ranking is therefore that of the elected. But you can use ```getStats()``` to get all initial scores table and outcomes scores. + +###### Quotas +Default quota is the Droop quota. Three others are available using the method options system _(see example below)_: Hare, Hagenbach-Bischoff, Imperiali. + +#### Code example + +```php +use CondorcetPHP\Condorcet\Algo\Tools\StvQuotas; + +// Change the number of seats +$election->setNumberOfSeats(42); # Default is 100 + +// Get the elected candidates with ranking +$election->getResult('STV'); + +// Check the number of seats +$election->getResult('STV')->getNumberOfSeats(); + +// Get Stats (votes needed to win, rounds detailsd) +$election->getResult('STV')->getStats(); + +// Change the Quota +$election->setMethodOption('STV', 'Quota', StvQuotas::HAGENBACH_BISCHOFF) ; +$election->getResult('STV') ; +$election->setMethodOption('STV', 'Quota', StvQuotas::IMPERIALI) ; +$election->getResult('STV') ; +$election->setMethodOption('STV', 'Quota', StvQuotas::HARE) ; +$election->getResult('STV') ; +$election->setMethodOption('STV', 'Quota', StvQuotas::DROOP) ; +$election->getResult('STV') ; +``` + + +### CPO-STV + +> **Family:** Single Transferable Vote +> **Proportional type:** Individual _(A candidate can be elected only once (1 seat). Ranking will never return many time the same candidate.)_ +> **Default STV Quota:** Hagenbach-Bischoff +> **Variant used:** *Completion method is Schulze Margin (default) then chaining different others methods if necessary* +> **Wikipedia:** https://en.wikipedia.org/wiki/CPO-STV +> *** +> **Methods alias available (for function call)**: "CPO STV" / "CPO_STV" / "CPO-STV" / "CPO" / "Comparison of Pairs of Outcomes by the Single Transferable Vote" / "Tideman STV" + +#### Implementation Comments +##### Fundamentals +- In case of tie into a vote rank, rank is ignored like he never existed. It's recommended to use the native vote constraint `NoTie` if you are not sure of your inputs: `$election->addConstraint(NoTie::class)`. +- The implementation of this method does not support parties. A candidate is elected only once, whatever the number of seats. +- Non-elected candidates are not included in the ranking. The ranking is therefore that of the elected. But you can use ```getStats()``` to get all initial scores table and outcomes scores. +- Ranking is sort by the initial score table, the winners with the same initial score are put back on the same rank in the same spirit of implementation as all other voting methods. + +###### Quotas +Default quota is the Hagenbach-Bischoff. Three others are available using the method options system _(see example below)_: Droop, Hare, Imperiali. + +###### Completion method +The best outcome is selected chaining methods in that order (first to deliver a single winner): ```SchulzeMargin → SchulzeWinning → SchulzeRatio → BordaCount → Copeland → InstantRunoff → MinimaxMargin → MinimaxWinning → DodgsonTidemanApproximation → FirstPastThePost``` +If none of them can deliver a single winner, the first one (default: ```SchulzeMargin```) is used, and one winner is arbitrarily chosen from the first rank. + +This order can be changed using option system _(see example below)_. + +###### Sorting score before electing +If more candidates than seats fill the quotas directly before outcome comparaison, then the ranking of elected candidates is ordered using the initial score table. If a tie persists, tie-breaker chaining concerning rank by chaining single-winner methods and comparing candidates. If this is not enough, use the alphabetical order. +Methods used to do it are the following in that order: ```SchulzeMargin → SchulzeWinning → SchulzeRatio → BordaCount → Copeland → InstantRunoff → MinimaxMargin → MinimaxWinning → DodgsonTidemanApproximation → FirstPastThePost``` +This can be changed by passing an option to the method, with an ordered array populated by method names. _(see example below)_ +Ranked-Pairs or Kemeny-Young are not used by default, because they are slow (or in practice impossible) for elections with many candidates, performance for them are not polynomials. + + +#### Code example + +```php +use CondorcetPHP\Condorcet\Algo\Tools\StvQuotas; + +// Change the number of seats +$election->setNumberOfSeats(7); # Default is 100 + +// Get the elected candidates with ranking +$election->getResult('CPO-STV'); + +// Check the number of seats +$election->getResult('CPO-STV')->getNumberOfSeats(); + +// Get Stats (votes needed to win, initial table score, candidate directly elected, outcomes score....) +$election->getResult('CPO-STV')->getStats(); // Resulting array can be really fat + +// Get all the stats (can slow down and use a lot of memory in some case, often related to the number of candidate to elect) +$election->->setStatsVerbosity(StatsVerbosity::FULL); +$election->getResult('CPO-STV')->getStats() ; + +// Change the Quota +$election->setMethodOption('CPO-STV', 'Quota', StvQuotas::HAGENBACH_BISCHOFF) ; +$election->getResult('CPO-STV') ; +$election->setMethodOption('CPO-STV', 'Quota', StvQuotas::IMPERIALI) ; +$election->getResult('CPO-STV') ; +$election->setMethodOption('CPO-STV', 'Quota', StvQuotas::HARE) ; +$election->getResult('CPO-STV') ; +$election->setMethodOption('CPO-STV', 'Quota', StvQuotas::DROOP) ; +$election->getResult('CPO-STV') ; + +// Change the completion selection method +$election->setMethodOption('CPO-STV', 'CondorcetCompletionMethod', [1=> 'Ranked Pairs', 2=> 'Kemeny-Young']) ; // Never use Ranked-Pairs or Kemeny-Young, they are too many outcomes to choose from, and their performances aren't polynomials. +$election->getResult('CPO-STV') ; + +// Change the sort method +$election->setMethodOption('CPO-STV', 'TieBreakerMethods', [1=> 'Ranked Pairs', 2=> 'Kemeny-Young']) ; +$election->getResult('CPO-STV') ; + +``` + +## Highest Averages Methods + +### Sainte-Laguë / Webster method + +> **Family:** Highest Averages Methods +> **Proportional type:** Party _(The same candidate can appear several times in the results.)_ +> **Variant used:** *Standard, but variants can be created with the options system as explained* +> **Wikipedia:** https://en.wikipedia.org/wiki/Webster/Sainte-Lagu%C3%AB_method +> *** +> **Methods alias available (for function call)**: "Sainte-Laguë", "SainteLague", "Webster", "Major Fractions Method" + +### Implementation Comments +- Accepts votes including full rankings, but only the first place will be evaluated. It's recommended to use the native vote constraint `NoTie` if you are not sure of your inputs: `$election->addConstraint(NoTie::class)`. +- In case of tie (more than one candidate) into the first vote rank, vote is ignored like he never existed. +- In case of a quotient tie in a round, candidates are selected arbitrarily. Not a problem most of the time, because unselected candidates will be chosen in the next round, except if a tie occurs on the last available seat. + + +### Code example + +```php +// Change the number of seats +$election->setNumberOfSeats(7); # Default is 100 + +// Get the elected candidates with ranking +$election->getResult('Sainte-Laguë'); # Return the usual ranking. Same candidate can appears several times, so it's a parties method. + +// Check the number of seats +$election->getResult('Sainte-Laguë')->getNumberOfSeats(); + +// Get Stats (sumup ranks) +$election->getResult('Sainte-Laguë')->getStats(); # Summarizes the number of seats. And details about each round. + +// Use Norwegian Variant +$this->election->setMethodOption('SainteLague', 'FirstDivisor', 1.4); +$election->getResult('Sainte-Laguë'); + +// Get back to the normal version +$this->election->setMethodOption('SainteLague', 'FirstDivisor', 1); +``` + +### Jefferson / D'Hondt method + +> **Family:** Highest Averages Methods +> **Proportional type:** Party _(The same candidate can appear several times in the results.)_ +> **Variant used:** *-* +> **Wikipedia:** https://en.wikipedia.org/wiki/D%27Hondt_method +> *** +> **Methods alias available (for function call)**: "Jefferson", "D'Hondt", "Thomas Jefferson" + +### Implementation Comments +- Accepts votes including full rankings, but only the first place will be evaluated. It's recommended to use the native vote constraint `NoTie` if you are not sure of your inputs: `$election->addConstraint(NoTie::class)`. +- In case of tie (more than one candidate) into the first vote rank, vote is ignored like he never existed. +- In case of a quotient tie in a round, candidates are selected arbitrarily. Not a problem most of the time, because unselected candidates will be chosen in the next round, except if a tie occurs on the last available seat. + +### Code example + +```php +// Change the number of seats +$election->setNumberOfSeats(7); # Default is 100 + +// Get the elected candidates with ranking +$election->getResult('Jefferson'); # Return the usual ranking. Same candidate can appears several times, so it's a parties method. + +// Check the number of seats +$election->getResult('Jefferson')->getNumberOfSeats(); + +// Get Stats (sumup ranks) +$election->getResult('Jefferson')->getStats(); # Summarizes the number of seats. And details about each round. +``` + + +## Largest Remainder Methods + +### Hare-LR / Droop-LR / Imperiali-LR / Hagenbach-Bischoff-LR + +> **Family:** Highest Averages Methods +> **Proportional type:** Party _(The same candidate can appear several times in the results.)_ +> **Also known as (when properly set):** Hare-LR / Droop-LR / Imperiali-LR / Hagenbach-Bischoff-LR +> **Variant used:** *-* +> **Default Quota:** Hare +> **Wikipedia:** https://en.wikipedia.org/wiki/Largest_remainder_method +> *** +> **Methods alias available (for function call)**: "Largest Remainder", "LargestRemainder", "LR", "Hare–Niemeyer method", "Hamilton method", "Vinton's method" + +### Implementation Comments +- Accepts votes including full rankings, but only the first place will be evaluated. It's recommended to use the native vote constraint `NoTie` if you are not sure of your inputs: `$election->addConstraint(NoTie::class)`. +- In case of tie (more than one candidate) into the first vote rank, vote is ignored like he never existed. +- In case of a quotient tie in a round, candidates are selected arbitrarily. Not a problem most of the time, because unselected candidates will be chosen in the next round, except if a tie occurs on the last available seat. + +### Code example + +```php +// Change the number of seats +$election->setNumberOfSeats(7); # Default is 100 + +// Get the elected candidates with ranking +$election->getResult('Largest Remainder'); # Return the usual ranking. Same candidate can appears several times, so it's a parties method. + +// Check the number of seats +$election->getResult('Largest Remainder')->getNumberOfSeats(); + +// Get Stats (sumup ranks) +$election->getResult('Largest Remainder')->getStats(); # Summarizes the number of seats. And details about each round. + +// Change the Quota +$election->setMethodOption('Largest Remainder', 'Quota', StvQuotas::HAGENBACH_BISCHOFF) ; +$election->getResult('Largest Remainder') ; +$election->setMethodOption('Largest Remainder', 'Quota', StvQuotas::IMPERIALI) ; +$election->getResult('Largest Remainder') ; +$election->setMethodOption('Largest Remainder', 'Quota', StvQuotas::HARE) ; +$election->getResult('Largest Remainder') ; +$election->setMethodOption('Largest Remainder', 'Quota', StvQuotas::DROOP) ; +$election->getResult('Largest Remainder') ; +``` \ No newline at end of file diff --git a/include/Condorcet/__CondorcetAutoload.php b/include/Condorcet/__CondorcetAutoload.php new file mode 100644 index 0000000..4a521e4 --- /dev/null +++ b/include/Condorcet/__CondorcetAutoload.php @@ -0,0 +1,44 @@ +')) { + trigger_error( + 'Condorcet PHP requires a PHP version greater than or equal to '.$condorcet_minimal_php_version.'. Your current version is '.\PHP_VERSION.'.', + \E_USER_ERROR + ); +} + +// Self Autoload function coming after and as a fallback of composer or other framework PSR autoload implementation. Composer or framework autoload will alway be will be preferred to that custom function. +spl_autoload_register(static function (string $class): void { + + // project-specific namespace prefix + $prefix = 'CondorcetPHP\Condorcet\\'; + + + // does the class use the namespace prefix? + $len = mb_strlen($prefix); + if (strncmp($prefix, $class, $len) !== 0) { + // no, move to the next registered autoloader + return; + } + + // get the relative class name + $relative_class = mb_substr($class, $len); + + // replace the namespace prefix with the base directory, replace namespace + // separators with directory separators in the relative class name, append + // with .php + $file = __DIR__ . \DIRECTORY_SEPARATOR . 'src' . \DIRECTORY_SEPARATOR . str_replace('\\', \DIRECTORY_SEPARATOR, $relative_class) . '.php'; + + // if the file exists, require it + if (file_exists($file)) { + require $file; + } +}); diff --git a/include/Condorcet/bin/condorcet b/include/Condorcet/bin/condorcet new file mode 100755 index 0000000..074905e --- /dev/null +++ b/include/Condorcet/bin/condorcet @@ -0,0 +1,3 @@ +#!/usr/bin/env php += 0.4.3", + "infection/infection": "^0.26", + "phpstan/phpstan": "^1.8", + "laravel/pint": "^1.1" + }, + "suggest": { + "ext-mbstring": "If you need to deal with Debian vote format", + "ext-pdo": "Allow to use database for very large elections.", + "ext-pdo_sqlite": "Use sqlite3 bases for very large elections." + }, + "autoload": { + "psr-4": { + "CondorcetPHP\\Condorcet\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "CondorcetPHP\\Condorcet\\Tests\\": "Tests/src/", + "CondorcetPHP\\Condorcet\\Tests\\Examples\\": "Tests/Examples/", + "CondorcetPHP\\Condorcet\\Dev\\CondorcetDocumentationGenerator\\": "Dev/CondorcetDocumentationGenerator/", + "CondorcetPHP\\Condorcet\\Benchmarks\\": "Benchmarks" + } + }, + "scripts": { + "test": "phpunit" + }, + "scripts-descriptions": { + "test": "Run all tests!" + }, + "bin": ["bin/condorcet"], + "archive": { + "exclude": [ + "/Assets", + "/Benchmarks", + "/Dev/", + "/Documentation", + "/Examples", + "/Tests", + "*.dist" + ] + }, + "config": { + "allow-plugins": { + "infection/extension-installer": false + } + } +} diff --git a/include/Condorcet/infection.json.dist b/include/Condorcet/infection.json.dist new file mode 100644 index 0000000..debe1d7 --- /dev/null +++ b/include/Condorcet/infection.json.dist @@ -0,0 +1,22 @@ +{ + "source": { + "directories": [ + "src" + ] + }, + "mutators": { + "@default": true, + "ProtectedVisibility": { + "ignore": ["*"] + } + }, + "logs": { + "text": "Dev/infection_logs/infection.log", + "summary": "Dev/infection_logs/summary.log", + "json": "Dev/infection_logs/infection-log.json", + "perMutator": "Dev/infection_logs/per-mutator.md", + "stryker": { + "badge": "/^release-.*$/" + } + } +} \ No newline at end of file diff --git a/include/Condorcet/phpbench.json.dist b/include/Condorcet/phpbench.json.dist new file mode 100644 index 0000000..baf584a --- /dev/null +++ b/include/Condorcet/phpbench.json.dist @@ -0,0 +1,3 @@ +{ + "runner.bootstrap": "vendor/autoload.php" +} \ No newline at end of file diff --git a/include/Condorcet/phpstan.neon.dist b/include/Condorcet/phpstan.neon.dist new file mode 100644 index 0000000..48b216d --- /dev/null +++ b/include/Condorcet/phpstan.neon.dist @@ -0,0 +1,13 @@ +parameters: + checkGenericClassInNonGenericObjectType: false + + ignoreErrors: + - '#Class .* has an uninitialized readonly property \$.* Assign it in the constructor.#' + - '#Access to an uninitialized readonly property#' + - '#Variable \$.* might not be defined.#' + - '#Unsafe access to private constant .* through static#' + - '#Readonly property .* is assigned outside of the constructor.#' + - '#no value type specified in iterable type array#' + - '#Exception::\$message has no type specified.#' + + - '#Readonly property .*\$CandidatesParsedFromVotes is already assigned.#' \ No newline at end of file diff --git a/include/Condorcet/phpunit.xml.dist b/include/Condorcet/phpunit.xml.dist new file mode 100644 index 0000000..89bdf7d --- /dev/null +++ b/include/Condorcet/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + + + ./src + + + + + ./Tests + + + + + large + + + diff --git a/include/Condorcet/pint.json b/include/Condorcet/pint.json new file mode 100644 index 0000000..80c8040 --- /dev/null +++ b/include/Condorcet/pint.json @@ -0,0 +1,106 @@ +{ + "preset": "psr12", + "rules": { + "array_indentation": true, + "array_push": true, + "array_syntax": true, + "cast_spaces": true, + "class_reference_name_casing": true, + "combine_consecutive_issets": true, + "constant_case": true, + "declare_strict_types": true, + "dir_constant": true, + "explicit_indirect_variable": true, + "explicit_string_variable": true, + "fully_qualified_strict_types": true, + "function_to_constant": true, + "function_typehint_space": true, + "get_class_to_class_keyword": true, + "group_import": true, + "heredoc_indentation": true, + "heredoc_to_nowdoc": true, + "implode_call": true, + "include": true, + "integer_literal_case": true, + "is_null": true, + "lambda_not_used_import": true, + "list_syntax": true, + "logical_operators": true, + "magic_constant_casing": true, + "magic_method_casing": true, + "mb_str_functions": true, + "method_chaining_indentation": true, + "modernize_strpos": true, + "native_constant_invocation": { + "strict": false + }, + "native_function_casing": true, + "native_function_invocation": true, + "native_function_type_declaration_casing": true, + "new_with_braces": { + "anonymous_class": false, + "named_class": false + }, + "no_alias_functions": true, + "no_alias_language_construct_call": true, + "no_alternative_syntax": true, + "no_empty_statement": true, + "no_homoglyph_names": true, + "no_leading_namespace_whitespace": true, + "no_multiline_whitespace_around_double_arrow": true, + "no_null_property_initialization": true, + "no_php4_constructor": true, + "no_short_bool_cast": true, + "no_singleline_whitespace_before_semicolons": true, + "no_spaces_around_offset": true, + "no_trailing_comma_in_list_call": true, + "no_trailing_comma_in_singleline_array": true, + "no_trailing_comma_in_singleline_function_call": true, + "no_unneeded_control_parentheses": true, + "no_unneeded_curly_braces": true, + "no_unneeded_import_alias": true, + "no_unset_on_property": true, + "no_unused_imports": true, + "no_whitespace_before_comma_in_array": true, + "normalize_index_brace": true, + "nullable_type_declaration_for_default_null_value": true, + "object_operator_without_whitespace": true, + "ordered_interfaces": true, + "php_unit_construct": true, + "php_unit_dedicate_assert": true, + "php_unit_no_expectation_annotation": true, + "php_unit_set_up_tear_down_visibility": true, + "pow_to_exponentiation": true, + "regular_callable_call": true, + "return_assignment": true, + "self_accessor": true, + "semicolon_after_instruction": true, + "set_type_to_cast": true, + "simple_to_complex_string_variable": true, + "single_class_element_per_statement": true, + "single_import_per_statement": false, + "single_quote": true, + "single_space_after_construct": true, + "space_after_semicolon": true, + "standardize_not_equals": true, + "static_lambda": true, + "strict_param": true, + "string_line_ending": true, + "switch_continue_to_break": true, + "ternary_to_elvis_operator": true, + "ternary_to_null_coalescing": true, + "trailing_comma_in_multiline": true, + "trim_array_spaces": true, + "types_spaces": true, + "void_return": true, + "whitespace_after_comma_in_array": true, + "yoda_style": { + "equal": false, + "identical": false, + "less_and_greater": false + } + }, + "exclude": [ + "Examples" + ] +} \ No newline at end of file diff --git a/include/Condorcet/src/Algo/Method.php b/include/Condorcet/src/Algo/Method.php new file mode 100644 index 0000000..87bcc30 --- /dev/null +++ b/include/Condorcet/src/Algo/Method.php @@ -0,0 +1,119 @@ +setElection($mother); + + if (static::$MaxCandidates !== null && $mother->countCandidates() > static::$MaxCandidates) { + throw new CandidatesMaxNumberReachedException(static::METHOD_NAME[0], static::$MaxCandidates); + } + } + + public function __serialize(): array + { + $r = get_object_vars($this); + unset($r['selfElection']); + + return $r; + } + + public function setElection(Election $election): void + { + $this->selfElection = \WeakReference::create($election); + } + + #[InternalModulesAPI] + public function getElection(): Election + { + return $this->selfElection->get(); // @phpstan-ignore-line + } + + #[InternalModulesAPI] + public function getResult(): Result + { + // Cache + if ($this->Result !== null) { + return $this->Result; + } + + $this->compute(); + + return $this->Result; + } + + protected function compute(): void + { + } + + abstract protected function getStats(): array; + + protected function createResult(array $result): Result + { + $optionsList = array_keys((new \ReflectionClass(static::class))->getStaticProperties()); + $optionsList = array_filter($optionsList, static function (string $name): bool { + return str_starts_with($name, 'option'); + }); + + $methodOptions = []; + + foreach ($optionsList as $oneOption) { + $methodOptions[mb_substr($oneOption, 6)] = static::${$oneOption}; + } + + return new Result( + fromMethod: static::METHOD_NAME[0], + byClass: $this::class, + election: $this->getElection(), + result: $result, + stats: ($this->getElection()->getStatsVerbosity()->value > StatsVerbosity::NONE->value) ? $this->getStats() : null, + seats: (static::IS_PROPORTIONAL) ? $this->getElection()->getNumberOfSeats() : null, + methodOptions: $methodOptions + ); + } +} diff --git a/include/Condorcet/src/Algo/MethodInterface.php b/include/Condorcet/src/Algo/MethodInterface.php new file mode 100644 index 0000000..46ba309 --- /dev/null +++ b/include/Condorcet/src/Algo/MethodInterface.php @@ -0,0 +1,25 @@ +getElection(); + $stats = []; + + foreach ($this->Stats as $candidateKey => $oneScore) { + $stats[(string) $election->getCandidateObjectFromKey($candidateKey)] = $oneScore; + } + + return $stats; + } + + + /////////// COMPUTE /////////// + + //:: BORDA ALGORITHM. ::// + + protected function compute(): void + { + $election = $this->getElection(); + $score = []; + + foreach (array_keys($election->getCandidatesList()) as $oneCandidateKey) { + $score[$oneCandidateKey] = 0; + } + + foreach ($election->getVotesManager()->getVotesValidUnderConstraintGenerator() as $oneVote) { + $CandidatesRanked = 0; + $oneRanking = $oneVote->getContextualRankingWithoutSort($election); + + foreach ($oneRanking as $oneRank) { + $rankScore = 0.0; + foreach ($oneRank as $oneCandidateInRank) { + $rankScore += $this->getScoreByCandidateRanking($CandidatesRanked++, $election); + } + + foreach ($oneRank as $oneCandidateInRank) { + $score[$election->getCandidateKey($oneCandidateInRank)] += ($rankScore / \count($oneRank)) * $oneVote->getWeight($election); + } + } + } + + array_walk($score, static fn (float &$sc): float => $sc = round($sc, self::DECIMAL_PRECISION)); + ksort($score, \SORT_NATURAL); + arsort($score, \SORT_NUMERIC); + + $rank = 0; + $lastScore = null; + $result = []; + foreach ($score as $candidateKey => $candidateScore) { + if ($candidateScore === $lastScore) { + $result[$rank][] = $candidateKey; + } else { + $result[++$rank] = [$candidateKey]; + $lastScore = $candidateScore; + } + } + + $this->Stats = $score; + $this->Result = $this->createResult($result); + } + + protected function getScoreByCandidateRanking(int $CandidatesRanked, Election $election): float + { + return (float) ($election->countCandidates() + static::$optionStarting - 1 - $CandidatesRanked); + } +} diff --git a/include/Condorcet/src/Algo/Methods/Borda/DowdallSystem.php b/include/Condorcet/src/Algo/Methods/Borda/DowdallSystem.php new file mode 100644 index 0000000..afc2177 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Borda/DowdallSystem.php @@ -0,0 +1,27 @@ +CondorcetWinner !== null) { + return $this->CondorcetWinner; + } + + // ------- + + // Basic Condorcet calculation + foreach ($this->getElection()->getPairwise() as $candidate_key => $candidat_detail) { + $winner = true; + + foreach ($candidat_detail['win'] as $challenger_key => $win_count) { + if ($win_count <= $candidat_detail['lose'][$challenger_key]) { + $winner = false; + break; + } + } + + if ($winner) { + $this->CondorcetWinner = $candidate_key; + return $this->CondorcetWinner; + } + } + + return null; + } + + // Get a Condorcet certified loser. If there is none = null. You can force a winner choice with alternative supported methods ($substitution) + public function getLoser(): ?int + { + // Cache + if ($this->CondorcetLoser !== null) { + return $this->CondorcetLoser; + } + + // ------- + + // Basic Condorcet calculation + foreach ($this->getElection()->getPairwise() as $candidate_key => $candidat_detail) { + $loser = true; + + foreach ($candidat_detail['lose'] as $challenger_key => $lose_count) { + if ($lose_count <= $candidat_detail['win'][$challenger_key]) { + $loser = false; + break; + } + } + + if ($loser) { + $this->CondorcetLoser = $candidate_key; + return $this->CondorcetLoser; + } + } + + return null; + } +} diff --git a/include/Condorcet/src/Algo/Methods/Copeland/Copeland.php b/include/Condorcet/src/Algo/Methods/Copeland/Copeland.php new file mode 100644 index 0000000..a2da8b4 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Copeland/Copeland.php @@ -0,0 +1,35 @@ +getElection(); + $stats = []; + + foreach ($this->Stats as $candidateKey => $dodgsonQuickValue) { + $stats[(string) $election->getCandidateObjectFromKey($candidateKey)] = $dodgsonQuickValue; + } + + return $stats; + } + + + /////////// COMPUTE /////////// + + //:: DODGSON ALGORITHM. ::// + + protected function compute(): void + { + $election = $this->getElection(); + + $pairwise = $election->getPairwise(); + $HeadToHead = []; + + foreach ($pairwise as $candidateId => $CandidateStats) { + foreach ($CandidateStats['lose'] as $opponentId => $CandidateLose) { + if (($diff = $CandidateLose - $CandidateStats['win'][$opponentId]) >= 0) { + $HeadToHead[$candidateId][$opponentId] = $diff; + } + } + } + + $dodgsonQuick = []; + + foreach ($HeadToHead as $candidateId => $CandidateTidemanScores) { + $dodgsonQuick[$candidateId] = 0; + + foreach ($CandidateTidemanScores as $oneTidemanScore) { + $dodgsonQuick[$candidateId] += ceil($oneTidemanScore / 2); + } + } + asort($dodgsonQuick); + + $rank = 0; + $result = []; + + if ($basicCondorcetWinner = $election->getCondorcetWinner()) { + $result[++$rank][] = $election->getCandidateKey($basicCondorcetWinner); + } + + $lastDodgsonQuickValue = null; + + foreach ($dodgsonQuick as $CandidateId => $dodgsonQuickValue) { + if ($lastDodgsonQuickValue === $dodgsonQuickValue) { + $result[$rank][] = $CandidateId; + } else { + $result[++$rank][] = $CandidateId; + $lastDodgsonQuickValue = $dodgsonQuickValue; + } + } + + $this->Stats = $dodgsonQuick; + $this->Result = $this->createResult($result); + } +} diff --git a/include/Condorcet/src/Algo/Methods/Dodgson/DodgsonTidemanApproximation.php b/include/Condorcet/src/Algo/Methods/Dodgson/DodgsonTidemanApproximation.php new file mode 100644 index 0000000..a3e8c10 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Dodgson/DodgsonTidemanApproximation.php @@ -0,0 +1,36 @@ +countVotesPerCandidates(); + + if (array_sum($this->candidatesVotes) > 0) { + foreach (array_keys($this->getElection()->getCandidatesList()) as $candidateKey) { + $this->candidatesSeats[$candidateKey] = 0; + } + + # Rounds + $this->Result = $this->createResult($this->makeRounds()); + } else { + $this->Result = $this->createResult([]); + } + } + + protected function countVotesPerCandidates(): void + { + $election = $this->getElection(); + + foreach (array_keys($election->getCandidatesList()) as $candidateKey) { + $this->candidatesVotes[$candidateKey] = 0; + } + + foreach ($election->getVotesValidUnderConstraintGenerator() as $oneVote) { + if ($oneVote->countRankingCandidates() === 0) { + continue; + } + + $voteWinnerRank = $oneVote->getContextualRankingWithoutSort($election)[1]; + + if (\count($voteWinnerRank) !== 1) { + continue; + } // This method support only one winner per vote. Ignore bad votes. + + $this->candidatesVotes[$election->getCandidateKey(reset($voteWinnerRank))] += $oneVote->getWeight($election); + } + } + + protected function makeRounds(): array + { + $election = $this->getElection(); + $results = []; + + while (array_sum($this->candidatesSeats) < $election->getNumberOfSeats()) { + $roundNumber = \count($this->rounds) + 1; + $maxQuotient = null; + $maxQuotientCandidateKey = null; + + foreach ($this->candidatesVotes as $candidateKey => $oneCandidateVotes) { + $quotient = $this->computeQuotient($oneCandidateVotes, $this->candidatesSeats[$candidateKey]); + + $this->rounds[$roundNumber][$candidateKey]['Quotient'] = $quotient; + $this->rounds[$roundNumber][$candidateKey]['NumberOfSeatsAllocatedBeforeRound'] = $this->candidatesSeats[$candidateKey]; + + if ($quotient > $maxQuotient) { + $maxQuotient = $quotient; + $maxQuotientCandidateKey = $candidateKey; + } + } + + $this->candidatesSeats[$maxQuotientCandidateKey]++; + $results[$roundNumber] = $maxQuotientCandidateKey; + } + + return $results; + } + + abstract protected function computeQuotient(int $votesWeight, int $seats): float; + + protected function getStats(): array + { + $election = $this->getElection(); + + $stats = []; + + if ($election->getStatsVerbosity()->value > StatsVerbosity::LOW->value) { + foreach ($this->rounds as $roundNumber => $oneRound) { + foreach ($oneRound as $candidateKey => $roundCandidateStats) { + $stats['Rounds'][$roundNumber][$election->getCandidateObjectFromKey($candidateKey)->getName()] = $roundCandidateStats; + } + } + } + + if ($election->getStatsVerbosity()->value > StatsVerbosity::NONE->value) { + foreach ($this->candidatesSeats as $candidateKey => $candidateSeats) { + $stats['Seats per Candidates'][$election->getCandidateObjectFromKey($candidateKey)->getName()] = $candidateSeats; + } + } + + return $stats; + } +} diff --git a/include/Condorcet/src/Algo/Methods/HighestAverages/Jefferson.php b/include/Condorcet/src/Algo/Methods/HighestAverages/Jefferson.php new file mode 100644 index 0000000..2d77950 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/HighestAverages/Jefferson.php @@ -0,0 +1,31 @@ +getElection(); + $stats = ['majority' => $this->majority]; + + foreach ($this->Stats as $oneIterationKey => $oneIterationData) { + if (\count($oneIterationData) === 1) { + break; + } + + foreach ($oneIterationData as $candidateKey => $candidateValue) { + $stats['rounds'][$oneIterationKey][(string) $election->getCandidateObjectFromKey($candidateKey)] = $candidateValue; + } + } + + return $stats; + } + + + /////////// COMPUTE /////////// + + //:: Alternative Vote ALGORITHM. ::// + + protected function compute(): void + { + $election = $this->getElection(); + + $candidateCount = $election->countCandidates(); + $this->majority = $election->sumValidVotesWeightWithConstraints() / 2; + + $candidateDone = []; + $result = []; + $CandidatesWinnerCount = 0; + $CandidatesLoserCount = 0; + + $iteration = 0; + + while (\count($candidateDone) < $candidateCount) { + $score = $this->makeScore($candidateDone); + $maxScore = max($score); + $minScore = min($score); + + $this->Stats[++$iteration] = $score; + + if ($maxScore > $this->majority) { + foreach ($score as $candidateKey => $candidateScore) { + if ($candidateScore !== $maxScore) { + continue; + } else { + $candidateDone[] = $candidateKey; + $result[++$CandidatesWinnerCount][] = $candidateKey; + } + } + } else { + $LosersToRegister = []; + + foreach ($score as $candidateKey => $candidateScore) { + if ($candidateScore !== $minScore) { + continue; + } else { + $LosersToRegister[] = $candidateKey; + } + } + + // Tie Breaking + $round = \count($LosersToRegister); + for ($i = 1; $i < $round; $i++) { // A little silly. But ultimately shorter and simpler. + $LosersToRegister = TieBreakersCollection::electSomeLosersbasedOnPairwiseComparaison($election, $LosersToRegister); + } + + $CandidatesLoserCount += \count($LosersToRegister); + array_push($candidateDone, ...$LosersToRegister); + $result[$candidateCount - $CandidatesLoserCount + 1] = $LosersToRegister; + } + } + + $this->Result = $this->createResult($result); + } + + protected function makeScore(array $candidateDone): array + { + $election = $this->getElection(); + $score = []; + + foreach ($election->getCandidatesList() as $candidateKey => $oneCandidate) { + if (!\in_array(needle: $candidateKey, haystack: $candidateDone, strict: true)) { + $score[$candidateKey] = 0; + } + } + + foreach ($election->getVotesManager()->getVotesValidUnderConstraintGenerator() as $oneVote) { + $weight = $oneVote->getWeight($election); + + foreach ($oneVote->getContextualRankingWithoutSort($election) as $oneRank) { + foreach ($oneRank as $oneCandidate) { + if (\count($oneRank) !== 1) { + break; + } elseif (!\in_array(needle: ($candidateKey = $election->getCandidateKey($oneCandidate)), haystack: $candidateDone, strict: true)) { + $score[$candidateKey] += $weight; + break 2; + } + } + } + } + + return $score; + } +} diff --git a/include/Condorcet/src/Algo/Methods/KemenyYoung/KemenyYoung.php b/include/Condorcet/src/Algo/Methods/KemenyYoung/KemenyYoung.php new file mode 100644 index 0000000..5265d6f --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/KemenyYoung/KemenyYoung.php @@ -0,0 +1,172 @@ +Result === null) { + $this->countElectionCandidates = $this->getElection()->countCandidates(); + $this->candidatesKey = array_keys($this->getElection()->getCandidatesList()); + $this->countPossibleRanking = Permutations::getPossibleCountOfPermutations($this->countElectionCandidates); + + $this->computeMaxAndConflicts(); + $this->makeRanking(); + $this->conflictInfos(); + } + + // Return + return $this->Result; + } + + + protected function getStats(): array + { + $election = $this->getElection(); + $stats = []; + + $stats['Best Score'] = $this->MaxScore; + $stats['Ranking In Conflicts'] = $this->Conflits > 0 ? $this->Conflits + 1 : $this->Conflits; + + if ($election->getStatsVerbosity()->value >= StatsVerbosity::FULL->value) { + $explicit = []; + + foreach ($this->getPossibleRankingIterator() as $key => $value) { + // Human readable + $i = 1; + foreach ($value as $candidate_key) { + $explicit[$key][$i++] = $election->getCandidateObjectFromKey($candidate_key)->getName(); + } + + $explicit[$key]['score'] = $this->computeOneScore($value, $election->getPairwise()); + } + + $stats['Ranking Scores'] = $explicit; + } + + return $stats; + } + + protected function conflictInfos(): void + { + if ($this->Conflits > 0) { + $this->Result->addWarning( + type: self::CONFLICT_WARNING_CODE, + msg: ($this->Conflits + 1).';'.$this->MaxScore + ); + } + } + + + /////////// COMPUTE /////////// + + + //:: Kemeny-Young ALGORITHM. ::// + + protected function getPossibleRankingIterator(): \Generator + { + $perm = new Permutations($this->candidatesKey); + + $key = 0; + foreach ($perm->getPermutationGenerator() as $onePermutation) { + yield $key++ => $onePermutation; + } + } + + + protected function computeMaxAndConflicts(): void + { + $pairwise = $this->getElection()->getPairwise(); + + foreach ($this->getPossibleRankingIterator() as $keyScore => $onePossibleRanking) { + $rankingScore = $this->computeOneScore($onePossibleRanking, $pairwise); + + // Max Ranking Score + if ($rankingScore > $this->MaxScore) { + $this->MaxScore = $rankingScore; + $this->Conflits = 0; + $this->bestRankingKey = $keyScore; + $this->bestRankingTab = $onePossibleRanking; + } elseif ($rankingScore === $this->MaxScore) { + $this->Conflits++; + } + } + } + + protected function computeOneScore(array $ranking, Pairwise $pairwise): int + { + $rankingScore = 0; + $do = []; + + foreach ($ranking as $candidateId) { + $do[] = $candidateId; + + foreach ($ranking as $rankCandidate) { + if (!\in_array(needle: $rankCandidate, haystack: $do, strict: true)) { + $rankingScore += $pairwise[$candidateId]['win'][$rankCandidate]; + } + } + } + + return $rankingScore; + } + + + /* + I do not know how in the very unlikely event that several possible classifications have the same highest score. + In the current state, one of them is chosen arbitrarily. + + See issue on Github : https://github.com/julien-boudry/Condorcet/issues/6 + */ + protected function makeRanking(): void + { + $winnerRanking = [null, ...$this->bestRankingTab]; + unset($winnerRanking[0]); + + $this->Result = $this->createResult($winnerRanking); + } +} diff --git a/include/Condorcet/src/Algo/Methods/LargestRemainder/LargestRemainder.php b/include/Condorcet/src/Algo/Methods/LargestRemainder/LargestRemainder.php new file mode 100644 index 0000000..c6133d1 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/LargestRemainder/LargestRemainder.php @@ -0,0 +1,70 @@ +getElection(); + $results = []; + $rescueCandidatesKeys = array_keys($election->getCandidatesList()); + reset($rescueCandidatesKeys); + + $quotient = $this->computeQuotient($election->sumValidVotesWeightWithConstraints(), $election->getNumberOfSeats()); + + while (array_sum($this->candidatesSeats) < $election->getNumberOfSeats()) { + $roundNumber = \count($this->rounds) + 1; + $maxVotes = null; + $maxVotesCandidateKey = null; + + foreach ($this->candidatesVotes as $candidateKey => $oneCandidateVotes) { + $this->rounds[$roundNumber][$candidateKey]['NumberOfVotesAllocatedBeforeRound'] = $oneCandidateVotes; + $this->rounds[$roundNumber][$candidateKey]['NumberOfSeatsAllocatedBeforeRound'] = $this->candidatesSeats[$candidateKey]; + + if ($oneCandidateVotes > $maxVotes) { + $maxVotes = $oneCandidateVotes; + $maxVotesCandidateKey = $candidateKey; + } + } + + if ($maxVotesCandidateKey === null) { + $n = current($rescueCandidatesKeys); + $maxVotesCandidateKey = $n; + next($rescueCandidatesKeys) !== false || reset($rescueCandidatesKeys); + } + + $this->candidatesVotes[$maxVotesCandidateKey] -= $quotient; + $this->candidatesSeats[$maxVotesCandidateKey]++; + $results[$roundNumber] = $maxVotesCandidateKey; + } + + return $results; + } + + protected function computeQuotient(int $votesWeight, int $seats): float + { + return self::$optionQuota->getQuota($votesWeight, $seats); + } +} diff --git a/include/Condorcet/src/Algo/Methods/Majority/FirstPastThePost.php b/include/Condorcet/src/Algo/Methods/Majority/FirstPastThePost.php new file mode 100644 index 0000000..7460959 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Majority/FirstPastThePost.php @@ -0,0 +1,25 @@ +getElection(); + $stats = []; + + foreach ($this->Stats as $roundNumber => $roundScore) { + foreach ($roundScore as $candidateKey => $oneScore) { + $stats[$roundNumber][(string) $election->getCandidateObjectFromKey($candidateKey)] = $oneScore; + } + } + + return $stats; + } + + + /////////// COMPUTE /////////// + + protected function compute(): void + { + $election = $this->getElection(); + + $round = 1; + $resolved = false; + $score = []; + + // Start a round + while ($resolved === false) { + $roundScore = $this->doOneRound(); + ksort($roundScore, \SORT_NATURAL); + arsort($roundScore, \SORT_NUMERIC); + + $score[$round] = $roundScore; + + if ($round === 1) { + foreach (array_keys($election->getCandidatesList()) as $oneCandidateKey) { + $score[$round][$oneCandidateKey] ??= 0.0; + } + } + + if ($round === $this->maxRound || reset($roundScore) > (array_sum($roundScore) / 2)) { + $resolved = true; + + if (isset($score[$round - 1]) && $score[$round] === $score[$round - 1]) { + unset($score[$round]); + } + } else { + $lastScore = null; + $nextRoundAddedCandidates = 0; + + $this->admittedCandidates = []; + + foreach ($roundScore as $oneCandidateKey => $oneScore) { + if ($lastScore === null || + $nextRoundAddedCandidates < ($this->targetNumberOfCandidatesForTheNextRound + ($this->numberOfTargetedCandidatesAfterEachRound * ($round - 1))) || + $oneScore === $lastScore + ) { + $this->admittedCandidates[] = $oneCandidateKey; + $lastScore = $oneScore; + $nextRoundAddedCandidates++; + } + } + } + + $round++; + } + + // Compute Ranking + $rank = 0; + $result = []; + krsort($score, \SORT_NUMERIC); + $doneCandidates = []; + + foreach ($score as $oneRound) { + $lastScore = null; + foreach ($oneRound as $candidateKey => $candidateScore) { + if (!\in_array(needle: $candidateKey, haystack: $doneCandidates, strict: true)) { + if ($candidateScore === $lastScore) { + $doneCandidates[] = $result[$rank][] = $candidateKey; + } else { + $result[++$rank] = [$doneCandidates[] = $candidateKey]; + $lastScore = $candidateScore; + } + } + } + } + + // Finalizing + ksort($score, \SORT_NUMERIC); + $this->Stats = $score; + $this->Result = $this->createResult($result); + } + + protected function doOneRound(): array + { + $election = $this->getElection(); + $roundScore = []; + + foreach ($election->getVotesValidUnderConstraintGenerator() as $oneVote) { + $weight = $oneVote->getWeight($election); + + $oneRanking = $oneVote->getContextualRankingWithoutSort($election); + + if (!empty($this->admittedCandidates)) { + foreach ($oneRanking as $rankKey => $oneRank) { + foreach ($oneRank as $InRankKey => $oneCandidate) { + if (!\in_array(needle: $election->getCandidateKey($oneCandidate), haystack: $this->admittedCandidates, strict: true)) { + unset($oneRanking[$rankKey][$InRankKey]); + } + } + + if (empty($oneRanking[$rankKey])) { + unset($oneRanking[$rankKey]); + } + } + + if (($newFirstRank = reset($oneRanking)) !== false) { + $oneRanking = [1 => $newFirstRank]; + } else { + continue; + } + } + + if (isset($oneRanking[1])) { + foreach ($oneRanking[1] as $oneCandidateInRank) { + $roundScore[$election->getCandidateKey($oneCandidateInRank)] ??= (float) 0; + $roundScore[$election->getCandidateKey($oneCandidateInRank)] += (1 / \count($oneRanking[1])) * $weight; + } + } + } + + array_walk($roundScore, static fn (float &$sc): float => $sc = round($sc, self::DECIMAL_PRECISION)); + + return $roundScore; + } +} diff --git a/include/Condorcet/src/Algo/Methods/Majority/MultipleRoundsSystem.php b/include/Condorcet/src/Algo/Methods/Majority/MultipleRoundsSystem.php new file mode 100644 index 0000000..28416d6 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Majority/MultipleRoundsSystem.php @@ -0,0 +1,36 @@ +maxRound = self::$optionMAX_ROUND; + $this->targetNumberOfCandidatesForTheNextRound = self::$optionTARGET_NUMBER_OF_CANDIDATES_FOR_THE_NEXT_ROUND; + $this->numberOfTargetedCandidatesAfterEachRound = self::$optionNUMBER_OF_TARGETED_CANDIDATES_AFTER_EACH_ROUND; + + parent::__construct($mother); + } +} diff --git a/include/Condorcet/src/Algo/Methods/Minimax/MinimaxMargin.php b/include/Condorcet/src/Algo/Methods/Minimax/MinimaxMargin.php new file mode 100644 index 0000000..388a029 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Minimax/MinimaxMargin.php @@ -0,0 +1,35 @@ +Result !== null) { + return $this->Result; + } + + // ------- + + // Comparison calculation + $this->Comparison = PairwiseStats::PairwiseComparison($this->getElection()->getPairwise()); + + // Ranking calculation + $this->makeRanking(); + + // Return + return $this->Result; + } + + + // Get the stats + protected function getStats(): array + { + $election = $this->getElection(); + $explicit = []; + + foreach ($this->Comparison as $candidate_key => $value) { + $explicit[$election->getCandidateObjectFromKey($candidate_key)->getName()] = [static::COUNT_TYPE => $value[static::COUNT_TYPE]]; + } + + return $explicit; + } + + + /////////// COMPUTE /////////// + + + //:: ALGORITHM. ::// + + protected function makeRanking(): void + { + $result = []; + + // Calculate ranking + $challenge = []; + $rank = 1; + $done = 0; + + foreach ($this->Comparison as $candidate_key => $candidate_data) { + $challenge[$candidate_key] = $candidate_data[static::COUNT_TYPE]; + } + + while ($done < $this->getElection()->countCandidates()) { + $looking = $this->looking($challenge); + + foreach ($challenge as $candidate => $value) { + if ($value === $looking) { + $result[$rank][] = $candidate; + + $done++; + unset($challenge[$candidate]); + } + } + + $rank++; + } + + $this->Result = $this->createResult($result); + } + + abstract protected function looking(array $challenge): int; +} diff --git a/include/Condorcet/src/Algo/Methods/RankedPairs/RankedPairsMargin.php b/include/Condorcet/src/Algo/Methods/RankedPairs/RankedPairsMargin.php new file mode 100644 index 0000000..12926cc --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/RankedPairs/RankedPairsMargin.php @@ -0,0 +1,23 @@ +Result !== null) { + return $this->Result; + } + + // ------- + + // Sort pairwise + $this->PairwiseSort = $this->pairwiseSort(); + + // Ranking calculation + $this->makeArcs(); + + // Make Stats + $this->Stats['tally'] = $this->PairwiseSort; + $this->Stats['arcs'] = $this->Arcs; + + // Make Result + return $this->Result = $this->createResult($this->makeResult()); + } + + // Get the Ranked Pair ranking + protected function getStats(): array + { + $election = $this->getElection(); + + if (!$this->StatsDone) { + foreach ($this->Stats['tally'] as &$Roundvalue) { + foreach ($Roundvalue as &$Arcvalue) { + foreach ($Arcvalue as $key => &$value) { + if ($key === 'from' || $key === 'to') { + $value = $election->getCandidateObjectFromKey($value)->getName(); + } + } + } + } + + foreach ($this->Stats['arcs'] as &$Arcvalue) { + foreach ($Arcvalue as $key => &$value) { + if ($key === 'from' || $key === 'to') { + $value = $election->getCandidateObjectFromKey($value)->getName(); + } + } + } + + $this->StatsDone = true; + } + + return $this->Stats; + } + + + /////////// COMPUTE /////////// + + + //:: RANKED PAIRS ALGORITHM. ::// + + protected function makeResult(): array + { + $election = $this->getElection(); + + $result = []; + $alreadyDone = []; + + $rang = 1; + while (\count($alreadyDone) < $election->countCandidates()) { + $winners = $this->getWinners($alreadyDone); + + foreach ($this->Arcs as $ArcKey => $Arcvalue) { + foreach ($winners as $oneWinner) { + if ($Arcvalue['from'] === $oneWinner || $Arcvalue['to'] === $oneWinner) { + unset($this->Arcs[$ArcKey]); + } + } + } + + $result[$rang++] = $winners; + array_push($alreadyDone, ...$winners); + } + + return $result; + } + + protected function getWinners(array $alreadyDone): array + { + $winners = []; + + foreach (array_keys($this->getElection()->getCandidatesList()) as $candidateKey) { + if (!\in_array(needle: $candidateKey, haystack: $alreadyDone, strict: true)) { + $win = true; + foreach ($this->Arcs as $ArcValue) { + if ($ArcValue['to'] === $candidateKey) { + $win = false; + } + } + + if ($win) { + $winners[] = $candidateKey; + } + } + } + + return $winners; + } + + + protected function makeArcs(): void + { + foreach ($this->PairwiseSort as $newArcsRound) { + $virtualArcs = $this->Arcs; + $testNewsArcs = []; + + $newKey = max((empty($highKey = array_keys($virtualArcs)) ? [-1] : $highKey)) + 1; + foreach ($newArcsRound as $newArc) { + $virtualArcs[$newKey] = ['from' => $newArc['from'], 'to' => $newArc['to']]; + $testNewsArcs[$newKey] = $virtualArcs[$newKey]; + $newKey++; + } + + foreach ($this->getArcsInCycle($virtualArcs) as $cycleArcKey) { + if (\array_key_exists($cycleArcKey, $testNewsArcs)) { + unset($testNewsArcs[$cycleArcKey]); + } + } + + foreach ($testNewsArcs as $newArc) { + $this->Arcs[] = $newArc; + } + } + } + + protected function getArcsInCycle(array $virtualArcs): array + { + $cycles = []; + + foreach (array_keys($this->getElection()->getCandidatesList()) as $candidateKey) { + array_push($cycles, ...$this->followCycle( + startCandidateKey: $candidateKey, + searchCandidateKey: $candidateKey, + virtualArcs: $virtualArcs + )); + } + + return $cycles; + } + + protected function followCycle(array $virtualArcs, int $startCandidateKey, int $searchCandidateKey, array &$done = []): array + { + $arcsInCycle = []; + + foreach ($virtualArcs as $ArcKey => $ArcValue) { + if ($ArcValue['from'] === $startCandidateKey) { + if (\in_array(needle: $ArcKey, haystack: $done, strict: true)) { + continue; + } elseif ($ArcValue['to'] === $searchCandidateKey) { + $done[] = $ArcKey; + $arcsInCycle[] = $ArcKey; + } else { + $done[] = $ArcKey; + array_push( + $arcsInCycle, + ...$this->followCycle( + startCandidateKey: $ArcValue['to'], + searchCandidateKey: $searchCandidateKey, + virtualArcs: $virtualArcs, + done: $done + ) + ); + } + } + } + + return $arcsInCycle; + } + + protected function pairwiseSort(): array + { + $pairs = []; + + $i = 0; + foreach ($this->getElection()->getPairwise() as $candidate_key => $candidate_value) { + foreach ($candidate_value['win'] as $challenger_key => $challenger_value) { + if ($challenger_value > $candidate_value['lose'][$challenger_key]) { + + // Victory + $pairs[$i]['from'] = $candidate_key; + // Defeat + $pairs[$i]['to'] = $challenger_key; + + $pairs[$i]['win'] = $challenger_value; + $pairs[$i]['minority'] = $candidate_value['lose'][$challenger_key]; + $pairs[$i]['margin'] = $candidate_value['win'][$challenger_key] - $candidate_value['lose'][$challenger_key]; + + $i++; + } + } + } + + usort($pairs, static function (array $a, array $b): int { + if ($a[static::RP_VARIANT_1] < $b[static::RP_VARIANT_1]) { + return 1; + } elseif ($a[static::RP_VARIANT_1] > $b[static::RP_VARIANT_1]) { + return -1; + } else { // Equal + return $a['minority'] <=> $b['minority']; + } + }); + + $newArcs = []; + $i = 0; + $f = true; + foreach (array_keys($pairs) as $pairsKey) { + if ($f === true) { + $newArcs[$i][] = $pairs[$pairsKey]; + $f = false; + } elseif ($pairs[$pairsKey][static::RP_VARIANT_1] === $pairs[$pairsKey - 1][static::RP_VARIANT_1] && $pairs[$pairsKey]['minority'] === $pairs[$pairsKey - 1]['minority']) { + $newArcs[$i][] = $pairs[$pairsKey]; + } else { + $newArcs[++$i][] = $pairs[$pairsKey]; + } + } + + return $newArcs; + } +} diff --git a/include/Condorcet/src/Algo/Methods/STV/CPO_STV.php b/include/Condorcet/src/Algo/Methods/STV/CPO_STV.php new file mode 100644 index 0000000..d1e2c30 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/STV/CPO_STV.php @@ -0,0 +1,387 @@ +outcomes = new SplFixedArray(0); + $this->outcomeComparisonTable = new SplFixedArray(0); + + $this->votesNeededToWin = round(self::$optionQuota->getQuota($this->getElection()->sumValidVotesWeightWithConstraints(), $this->getElection()->getNumberOfSeats()), self::DECIMAL_PRECISION, \PHP_ROUND_HALF_DOWN); + + // Compute Initial Score + $this->initialScoreTable = $this->makeScore(); + + // Candidates elected from first round + foreach ($this->initialScoreTable as $candidateKey => $oneScore) { + if ($oneScore >= $this->votesNeededToWin) { + $this->candidatesElectedFromFirstRound[] = $candidateKey; + } + } + + $numberOfCandidatesNeededToComplete = $this->getElection()->getNumberOfSeats() - \count($this->candidatesElectedFromFirstRound); + $this->candidatesEliminatedFromFirstRound = array_diff(array_keys($this->getElection()->getCandidatesList()), $this->candidatesElectedFromFirstRound); + + if ($numberOfCandidatesNeededToComplete > 0 && $numberOfCandidatesNeededToComplete < \count($this->candidatesEliminatedFromFirstRound)) { + try { + $numberOfComparisons = Combinations::getPossibleCountOfCombinations( + count: Combinations::getPossibleCountOfCombinations( + count: \count($this->candidatesEliminatedFromFirstRound), + length: $numberOfCandidatesNeededToComplete + ), + length: 2 + ); + } catch (IntegerOverflowException) { + $numberOfComparisons = false; + } + + if ($numberOfComparisons === false || (self::$MaxOutcomeComparisons !== null && $numberOfComparisons > self::$MaxOutcomeComparisons)) { + throw new MethodLimitReachedException(self::METHOD_NAME[0], self::METHOD_NAME[1].' is currently limited to '.self::$MaxOutcomeComparisons.' comparisons in order to avoid unreasonable deadlocks due to non-polyminial runtime aspects of the algorithm. Consult the manual to increase or remove this limit.'); + } + + + // Compute all possible Ranking + $this->outcomes = Combinations::compute($this->candidatesEliminatedFromFirstRound, $numberOfCandidatesNeededToComplete, $this->candidatesElectedFromFirstRound); + + // Compare it + $this->outcomeComparisonTable->setSize($numberOfComparisons); + $this->compareOutcomes(); + + // Select the best with a Condorcet method + $this->selectBestOutcome(); + $result = $this->outcomes[$this->condorcetWinnerOutcome]; + + // Sort the best Outcome candidate list using originals scores + usort($result, function (int $a, int $b): int { + return $this->initialScoreTable[$b] <=> $this->initialScoreTable[$a]; + }); + } else { + $result = array_keys($this->initialScoreTable); + + // Sort the best Outcome candidate list using originals scores, or using others methods + $this->sortResultBeforeCut($result); + + // Cut + $result = \array_slice($result, 0, $this->getElection()->getNumberOfSeats()); + } + + // Results: Format Ranks from 1 + $rank = 0; + $lastScore = null; + $candidatesDoneCount = 0; + $r = []; + foreach ($result as $candidateKey) { + $score = $this->initialScoreTable[$candidateKey]; + + if ($score !== $lastScore) { + $rank = $candidatesDoneCount + 1; + $lastScore = $score; + } + + $r[$rank][] = $candidateKey; + $candidatesDoneCount++; + } + + // Register result + $this->Result = $this->createResult($r); + + Vote::clearCache(); // Performances + } + + protected function compareOutcomes(): void + { + $election = $this->getElection(); + $index = 0; + $key_done = []; + + foreach ($this->outcomes as $MainOutcomeKey => $MainOutcomeR) { + foreach ($this->outcomes as $ComparedOutcomeKey => $ComparedOutcomeR) { + $outcomeComparisonKey = $this->getOutcomesComparisonKey($MainOutcomeKey, $ComparedOutcomeKey); + + if ($MainOutcomeKey === $ComparedOutcomeKey || \in_array($outcomeComparisonKey, $key_done, true)) { + continue; + } + + $entry = ['c_key' => $outcomeComparisonKey, + 'candidates_excluded' => [], + ]; + + // Eliminate Candidates from Outcome + foreach (array_keys($election->getCandidatesList()) as $candidateKey) { + if (!\in_array($candidateKey, $MainOutcomeR, true) && !\in_array($candidateKey, $ComparedOutcomeR, true)) { + $entry['candidates_excluded'][] = $candidateKey; + } + } + + // Make score again + $entry['scores_after_exclusion'] = $this->makeScore(candidateEliminated: $entry['candidates_excluded']); + + $surplusToTransfer = []; + $winnerToJoin = []; + foreach ($entry['scores_after_exclusion'] as $candidateKey => $oneScore) { + $surplus = $oneScore - $this->votesNeededToWin; + + if ($surplus >= 0 && \in_array($candidateKey, $MainOutcomeR, true) && \in_array($candidateKey, $ComparedOutcomeR, true)) { + $surplusToTransfer[$candidateKey] ?? $surplusToTransfer[$candidateKey] = ['surplus' => 0, 'total' => 0]; + $surplusToTransfer[$candidateKey]['surplus'] += $surplus; + $surplusToTransfer[$candidateKey]['total'] += $oneScore; + $winnerToJoin[$candidateKey] = $this->votesNeededToWin; + } + } + + $winnerFromFirstRound = array_keys($winnerToJoin); + + $entry['scores_after_surplus'] = $winnerToJoin + $this->makeScore($surplusToTransfer, $winnerFromFirstRound, $entry['candidates_excluded']); + + // Outcome Score + $MainOutcomeScore = 0; + $ComparedOutcomeScore = 0; + + foreach ($entry['scores_after_surplus'] as $candidateKey => $candidateScore) { + if (\in_array($candidateKey, $MainOutcomeR, true)) { + $MainOutcomeScore += $candidateScore; + } + + if (\in_array($candidateKey, $ComparedOutcomeR, true)) { + $ComparedOutcomeScore += $candidateScore; + } + } + + $entry['outcomes_scores'] = [$MainOutcomeKey => $MainOutcomeScore, $ComparedOutcomeKey => $ComparedOutcomeScore]; + + $key_done[] = $outcomeComparisonKey; + $this->outcomeComparisonTable[$index++] = $entry; + } + } + } + + protected function getOutcomesComparisonKey(int $MainOutcomeKey, int $ComparedOutcomeKey): string + { + $minOutcome = (string) min($MainOutcomeKey, $ComparedOutcomeKey); + $maxOutcome = (string) max($MainOutcomeKey, $ComparedOutcomeKey); + + return 'Outcome N° '.$minOutcome.' compared to Outcome N° '.$maxOutcome; + } + + protected function selectBestOutcome(): void + { + // With Condorcet + $winnerOutcomeElection = new Election; + $winnerOutcomeElection->setImplicitRanking(false); + $winnerOutcomeElection->allowsVoteWeight(true); + $winnerOutcomeElection->setStatsVerbosity($this->getElection()->getStatsVerbosity()); + + // Candidates + foreach ($this->outcomes as $oneOutcomeKey => $outcomeValue) { + $winnerOutcomeElection->addCandidate((string) $oneOutcomeKey); + } + + // Votes + $coef = Method::DECIMAL_PRECISION ** 10; # Actually, vote weight does not support float + foreach ($this->outcomeComparisonTable as $comparison) { + ($vote1 = new Vote([ + (string) $key = array_key_first($comparison['outcomes_scores']), + (string) array_key_last($comparison['outcomes_scores']), + ]))->setWeight((int) ($comparison['outcomes_scores'][$key] * $coef)); + + ($vote2 = new Vote([ + (string) $key = array_key_last($comparison['outcomes_scores']), + (string) array_key_first($comparison['outcomes_scores']), + ]))->setWeight((int) ($comparison['outcomes_scores'][$key] * $coef)); + + $winnerOutcomeElection->addVote($vote1); + $winnerOutcomeElection->addVote($vote2); + } + + // Selection Winner + $selectionSucces = false; + + foreach (self::$optionCondorcetCompletionMethod as $completionMethod) { + $completionMethodResult = $winnerOutcomeElection->getResult($completionMethod); + $condorcetWinnerOutcome = $completionMethodResult->getWinner(); + + if (!\is_array($condorcetWinnerOutcome)) { + $selectionSucces = true; + $this->completionMethodResult = $completionMethodResult; + break; + } + } + + if (!$selectionSucces) { + $completionMethodResult = $winnerOutcomeElection->getResult(self::$optionCondorcetCompletionMethod[0]); + $condorcetWinnerOutcome = $completionMethodResult->getWinner(); + $condorcetWinnerOutcome = reset($condorcetWinnerOutcome); + $this->completionMethodResult = $completionMethodResult; + } + + $this->condorcetWinnerOutcome = (int) $condorcetWinnerOutcome->getName(); + $this->completionMethodPairwise = $winnerOutcomeElection->getExplicitPairwise(); + } + + protected function sortResultBeforeCut(array &$result): void + { + usort($result, function (int $a, int $b): int { + $tieBreakerFromInitialScore = $this->initialScoreTable[$b] <=> $this->initialScoreTable[$a]; + + if ($tieBreakerFromInitialScore !== 0) { + return $tieBreakerFromInitialScore; + } else { + $election = $this->getElection(); + + if (\count($tiebreaker = TieBreakersCollection::tieBreakerWithAnotherMethods($election, self::$optionTieBreakerMethods, [$a, $b])) === 1) { + $w = reset($tiebreaker); + return ($w === $a) ? -1 : 1; + } else { + return mb_strtolower($election->getCandidateObjectFromKey($b)->getName(), 'UTF-8') <=> mb_strtolower($election->getCandidateObjectFromKey($b)->getName(), 'UTF-8'); + } + } + }); + } + + // Stats + + protected function getStats(): array + { + $election = $this->getElection(); + + $stats = ['Votes Needed to Win' => $this->votesNeededToWin]; + + $changeKeyToCandidateAndSortByName = static function (array $arr, Election $election): array { + $r = []; + foreach ($arr as $candidateKey => $value) { + $r[(string) $election->getCandidateObjectFromKey($candidateKey)] = $value; + } + + ksort($r, \SORT_NATURAL); + return $r; + }; + + $changeValueToCandidateAndSortByName = static function (array $arr, Election $election): array { + $r = []; + foreach ($arr as $candidateKey) { + $r[] = (string) $election->getCandidateObjectFromKey($candidateKey); + } + + sort($r, \SORT_NATURAL); + return $r; + }; + + // Stats >= STD + if ($election->getStatsVerbosity()->value >= StatsVerbosity::STD->value) { + // Initial Scores Table + $stats['Initial Score Table'] = $changeKeyToCandidateAndSortByName($this->initialScoreTable, $election); + + // Candidates Elected from first round + $stats['Candidates elected from first round'] = $changeValueToCandidateAndSortByName($this->candidatesElectedFromFirstRound, $election); + + // Candidates Eliminated from first round + $stats['Candidates eliminated from first round'] = $changeValueToCandidateAndSortByName($this->candidatesEliminatedFromFirstRound, $election); + + // Completion Method + if (isset($this->completionMethodResult)) { + $stats['Completion Method'] = $this->completionMethodResult->fromMethod; + } + } + + // Stats >= HIGH + if ($election->getStatsVerbosity()->value >= StatsVerbosity::HIGH->value) { + // Completion method Stats + if (isset($this->completionMethodResult)) { + $stats['Condorcet Completion Method Stats'] = [ + 'Pairwise' => $this->completionMethodPairwise, + 'Stats' => $this->completionMethodResult->getStats(), + ]; + } + } + + // Stats >= FULL + if ($election->getStatsVerbosity()->value >= StatsVerbosity::FULL->value) { + // Outcome + foreach ($this->outcomes as $outcomeKey => $outcomeValue) { + $stats['Outcomes'][$outcomeKey] = $changeValueToCandidateAndSortByName($outcomeValue, $election); + } + + // Outcomes Comparison + foreach ($this->outcomeComparisonTable as $octValue) { + foreach ($octValue as $octDetailsKey => $octDetailsValue) { + if ($octDetailsKey === 'candidates_excluded') { + $stats['Outcomes Comparison'][$octValue['c_key']][$octDetailsKey] = $changeValueToCandidateAndSortByName($octDetailsValue, $election); + } elseif ($octDetailsKey === 'outcomes_scores') { + $stats['Outcomes Comparison'][$octValue['c_key']][$octDetailsKey] = $octDetailsValue; + } elseif (\is_array($octDetailsValue)) { + $stats['Outcomes Comparison'][$octValue['c_key']][$octDetailsKey] = $changeKeyToCandidateAndSortByName($octDetailsValue, $election); + } + } + } + } + + // Return + return $stats; + } +} diff --git a/include/Condorcet/src/Algo/Methods/STV/SingleTransferableVote.php b/include/Condorcet/src/Algo/Methods/STV/SingleTransferableVote.php new file mode 100644 index 0000000..150d7c3 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/STV/SingleTransferableVote.php @@ -0,0 +1,176 @@ +getElection(); + Vote::initCache(); // Performances + + $result = []; + $rank = 0; + + $this->votesNeededToWin = round(self::$optionQuota->getQuota($election->sumValidVotesWeightWithConstraints(), $election->getNumberOfSeats()), self::DECIMAL_PRECISION, \PHP_ROUND_HALF_DOWN); + + $candidateElected = []; + $candidateEliminated = []; + + $end = false; + $round = 0; + + $surplusToTransfer = []; + + while (!$end) { + $scoreTable = $this->makeScore($surplusToTransfer, $candidateElected, $candidateEliminated); + ksort($scoreTable, \SORT_NATURAL); + arsort($scoreTable, \SORT_NUMERIC); + + $successOnRank = false; + + foreach ($scoreTable as $candidateKey => $oneScore) { + $surplus = $oneScore - $this->votesNeededToWin; + + if ($surplus >= 0) { + $result[++$rank] = [$candidateKey]; + $candidateElected[] = $candidateKey; + + $surplusToTransfer[$candidateKey] ?? $surplusToTransfer[$candidateKey] = ['surplus' => 0, 'total' => 0]; + $surplusToTransfer[$candidateKey]['surplus'] += $surplus; + $surplusToTransfer[$candidateKey]['total'] += $oneScore; + $successOnRank = true; + } + } + + if (!$successOnRank && !empty($scoreTable)) { + $candidateEliminated[] = array_key_last($scoreTable); + } elseif (empty($scoreTable) || $rank >= $election->getNumberOfSeats()) { + $end = true; + } + + $this->Stats[++$round] = $scoreTable; + } + + while ($rank < $election->getNumberOfSeats() && !empty($candidateEliminated)) { + $rescueCandidateKey = array_key_last($candidateEliminated); + $result[++$rank] = $candidateEliminated[$rescueCandidateKey]; + unset($candidateEliminated[$rescueCandidateKey]); + } + + $this->Result = $this->createResult($result); + + Vote::clearCache(); // Performances + } + + protected function makeScore(array $surplus = [], array $candidateElected = [], array $candidateEliminated = []): array + { + $election = $this->getElection(); + $scoreTable = []; + + $candidateDone = array_merge($candidateElected, $candidateEliminated); + + foreach (array_keys($election->getCandidatesList()) as $oneCandidateKey) { + if (!\in_array($candidateKey = $oneCandidateKey, $candidateDone, true)) { + $scoreTable[$candidateKey] = 0.0; + } + } + + foreach ($election->getVotesValidUnderConstraintGenerator() as $oneVote) { + $weight = $oneVote->getWeight($election); + + $winnerBonusWeight = 0; + $winnerBonusKey = null; + $LoserBonusWeight = 0; + + $firstRank = true; + foreach ($oneVote->getContextualRankingWithoutSort($election) as $oneRank) { + foreach ($oneRank as $oneCandidate) { + if (\count($oneRank) !== 1) { + break; + } + + $candidateKey = $election->getCandidateKey($oneCandidate); + + if ($firstRank) { + if (\array_key_exists($candidateKey, $surplus)) { + $winnerBonusWeight = $weight; + $winnerBonusKey = $candidateKey; + $firstRank = false; + break; + } elseif (\in_array($candidateKey, $candidateEliminated, true)) { + $LoserBonusWeight = $weight; + $firstRank = false; + break; + } + } + + if (\array_key_exists($candidateKey, $scoreTable)) { + if ($winnerBonusKey !== null) { + $scoreTable[$candidateKey] += $winnerBonusWeight / $surplus[$winnerBonusKey]['total'] * $surplus[$winnerBonusKey]['surplus']; + } elseif ($LoserBonusWeight > 0) { + $scoreTable[$candidateKey] += $LoserBonusWeight; + } else { + $scoreTable[$candidateKey] += $weight; + } + + $scoreTable[$candidateKey] = round($scoreTable[$candidateKey], self::DECIMAL_PRECISION, \PHP_ROUND_HALF_DOWN); + + break 2; + } + } + } + } + + return $scoreTable; + } + + protected function getStats(): array + { + $election = $this->getElection(); + + $stats = ['Votes Needed to Win' => $this->votesNeededToWin]; + + if ($election->getStatsVerbosity()->value > StatsVerbosity::LOW->value) { + $stats['rounds'] = []; + + foreach ($this->Stats as $roundNumber => $roundData) { + foreach ($roundData as $candidateKey => $candidateValue) { + $stats['rounds'][$roundNumber][(string) $election->getCandidateObjectFromKey($candidateKey)] = $candidateValue; + } + } + } + + return $stats; + } +} diff --git a/include/Condorcet/src/Algo/Methods/Schulze/SchulzeMargin.php b/include/Condorcet/src/Algo/Methods/Schulze/SchulzeMargin.php new file mode 100644 index 0000000..fb9c88a --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Schulze/SchulzeMargin.php @@ -0,0 +1,27 @@ +getPairwise()[$i]['win'][$j] - $election->getPairwise()[$j]['win'][$i]; + } +} diff --git a/include/Condorcet/src/Algo/Methods/Schulze/SchulzeRatio.php b/include/Condorcet/src/Algo/Methods/Schulze/SchulzeRatio.php new file mode 100644 index 0000000..445cc98 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Schulze/SchulzeRatio.php @@ -0,0 +1,31 @@ +getPairwise()[$j]['win'][$i] !== 0) { + return (float) ($election->getPairwise()[$i]['win'][$j] / $election->getPairwise()[$j]['win'][$i]); + } else { + return (float) (($election->getPairwise()[$i]['win'][$j] +1) / 1); + } + } +} diff --git a/include/Condorcet/src/Algo/Methods/Schulze/SchulzeWinning.php b/include/Condorcet/src/Algo/Methods/Schulze/SchulzeWinning.php new file mode 100644 index 0000000..ef28006 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Schulze/SchulzeWinning.php @@ -0,0 +1,27 @@ +getPairwise()[$i]['win'][$j]; + } +} diff --git a/include/Condorcet/src/Algo/Methods/Schulze/Schulze_Core.php b/include/Condorcet/src/Algo/Methods/Schulze/Schulze_Core.php new file mode 100644 index 0000000..8216c21 --- /dev/null +++ b/include/Condorcet/src/Algo/Methods/Schulze/Schulze_Core.php @@ -0,0 +1,178 @@ +Result !== null) { + return $this->Result; + } + + // ------- + + // Format array + $this->prepareStrongestPath(); + + // Strongest Paths calculation + $this->makeStrongestPaths(); + + // Ranking calculation + $this->makeRanking(); + + + // Return + return $this->Result; + } + + + // Get the Schulze ranking + protected function getStats(): array + { + $election = $this->getElection(); + $explicit = []; + + foreach ($this->StrongestPaths as $candidate_key => $candidate_value) { + $candidate_key = $election->getCandidateObjectFromKey($candidate_key)->getName(); + + foreach ($candidate_value as $challenger_key => $challenger_value) { + $explicit[$candidate_key][$election->getCandidateObjectFromKey($challenger_key)->getName()] = $challenger_value; + } + } + + return $explicit; + } + + + + /////////// COMPUTE /////////// + + + //:: SCHULZE ALGORITHM. ::// + + + // Calculate the strongest Paths for Schulze Method + protected function prepareStrongestPath(): void + { + $election = $this->getElection(); + $CandidatesKeys = array_keys($election->getCandidatesList()); + + foreach ($CandidatesKeys as $candidate_key) { + $this->StrongestPaths[$candidate_key] = []; + + // Format array for the strongest path + foreach ($CandidatesKeys as $candidate_key_r) { + if ($candidate_key_r !== $candidate_key) { + $this->StrongestPaths[$candidate_key][$candidate_key_r] = 0; + } + } + } + } + + + // Calculate the Strongest Paths + protected function makeStrongestPaths(): void + { + $election = $this->getElection(); + $CandidatesKeys = array_keys($election->getCandidatesList()); + + foreach ($CandidatesKeys as $i) { + foreach ($CandidatesKeys as $j) { + if ($i !== $j) { + if ($election->getPairwise()[$i]['win'][$j] > $election->getPairwise()[$j]['win'][$i]) { + $this->StrongestPaths[$i][$j] = $this->schulzeVariant($i, $j, $election); + } else { + $this->StrongestPaths[$i][$j] = 0; + } + } + } + } + + foreach ($CandidatesKeys as $i) { + foreach ($CandidatesKeys as $j) { + if ($i !== $j) { + foreach ($CandidatesKeys as $k) { + if ($i !== $k && $j !== $k) { + $this->StrongestPaths[$j][$k] = + max( + $this->StrongestPaths[$j][$k], + min($this->StrongestPaths[$j][$i], $this->StrongestPaths[$i][$k]) + ); + } + } + } + } + } + } + + + // Calculate && Format human readable ranking + protected function makeRanking(): void + { + $election = $this->getElection(); + $result = []; + + // Calculate ranking + $done = []; + $rank = 1; + + while (\count($done) < $election->countCandidates()) { + $to_done = []; + + foreach ($this->StrongestPaths as $candidate_key => $challengers_key) { + if (\in_array(needle: $candidate_key, haystack: $done, strict: true)) { + continue; + } + + $winner = true; + + foreach ($challengers_key as $beaten_key => $beaten_value) { + if (\in_array(needle: $beaten_key, haystack: $done, strict: true)) { + continue; + } + + if ($beaten_value < $this->StrongestPaths[$beaten_key][$candidate_key]) { + $winner = false; + } + } + + if ($winner) { + $result[$rank][] = $candidate_key; + + $to_done[] = $candidate_key; + } + } + + array_push($done, ...$to_done); + + $rank++; + } + + $this->Result = $this->createResult($result); + } +} diff --git a/include/Condorcet/src/Algo/Pairwise.php b/include/Condorcet/src/Algo/Pairwise.php new file mode 100644 index 0000000..e7add7c --- /dev/null +++ b/include/Condorcet/src/Algo/Pairwise.php @@ -0,0 +1,232 @@ +Pairwise[$offset]); + } + + public function offsetUnset(mixed $offset): void + { + } + + public function offsetGet(mixed $offset): ?array + { + return $this->Pairwise[$offset] ?? null; + } + + + // Implement Iterator + private bool $valid = true; + + public function rewind(): void + { + reset($this->Pairwise); + $this->valid = true; + } + + public function current(): array + { + return $this->Pairwise[$this->key()]; + } + + public function key(): ?int + { + return key($this->Pairwise); + } + + public function next(): void + { + if (next($this->Pairwise) === false) { + $this->valid = false; + } + } + + public function valid(): bool + { + return $this->valid; + } + + + // Pairwise + + protected \WeakReference $Election; + protected array $Pairwise_Model; + protected array $Pairwise; + + public function __construct(Election $link) + { + $this->setElection($link); + $this->formatNewpairwise(); + $this->doPairwise(); + } + + public function __serialize(): array + { + return [ + 'Pairwise_Model' => $this->Pairwise_Model, + 'Pairwise' => $this->Pairwise, + ]; + } + + public function getElection(): Election + { + return $this->Election->get(); + } + + public function setElection(Election $election): void + { + $this->Election = \WeakReference::create($election); + } + + public function addNewVote(int $key): void + { + (Condorcet::$UseTimer === true) && new Timer_Chrono($this->getElection()->getTimerManager(), 'Add Vote To Pairwise'); + + $this->computeOneVote($this->Pairwise, $this->getElection()->getVotesManager()[$key]); + } + + public function removeVote(int $key): void + { + (Condorcet::$UseTimer === true) && new Timer_Chrono($this->getElection()->getTimerManager(), 'Remove Vote To Pairwise'); + + $diff = $this->Pairwise_Model; + + $this->computeOneVote($diff, $this->getElection()->getVotesManager()[$key]); + + foreach ($diff as $candidate_key => $candidate_details) { + foreach ($candidate_details as $type => $opponent) { + foreach ($opponent as $opponent_key => $score) { + $this->Pairwise[$candidate_key][$type][$opponent_key] -= $score; + } + } + } + } + + #[PublicAPI] + #[Description('Return the Pairwise.')] + #[FunctionReturn('Pairwise as an explicit array .')] + #[Related('Election::getPairwise', 'Election::getResult')] + public function getExplicitPairwise(): array + { + $election = $this->getElection(); + $explicit_pairwise = []; + + foreach ($this->Pairwise as $candidate_key => $candidate_value) { + $candidate_name = $election->getCandidateObjectFromKey($candidate_key)->getName(); + + foreach ($candidate_value as $mode => $mode_value) { + foreach ($mode_value as $candidate_list_key => $candidate_list_value) { + $explicit_pairwise[$candidate_name][$mode][$election->getCandidateObjectFromKey($candidate_list_key)->getName()] = $candidate_list_value; + } + } + } + + return $explicit_pairwise; + } + + protected function formatNewpairwise(): void + { + $election = $this->getElection(); + $this->Pairwise_Model = []; + + foreach ($election->getCandidatesList() as $candidate_key => $candidate_id) { + $this->Pairwise_Model[$candidate_key] = ['win' => [], 'null' => [], 'lose' => []]; + + foreach ($election->getCandidatesList() as $candidate_key_r => $candidate_id_r) { + if ($candidate_key_r !== $candidate_key) { + $this->Pairwise_Model[$candidate_key]['win'][$candidate_key_r] = 0; + $this->Pairwise_Model[$candidate_key]['null'][$candidate_key_r] = 0; + $this->Pairwise_Model[$candidate_key]['lose'][$candidate_key_r] = 0; + } + } + } + } + + protected function doPairwise(): void + { + $election = $this->getElection(); + + // Chrono + (Condorcet::$UseTimer === true) && new Timer_Chrono($election->getTimerManager(), 'Do Pairwise'); + + $this->Pairwise = $this->Pairwise_Model; + + foreach ($election->getVotesManager()->getVotesValidUnderConstraintGenerator() as $oneVote) { + $this->computeOneVote($this->Pairwise, $oneVote); + } + } + + protected function computeOneVote(array &$pairwise, Vote $oneVote): void + { + $election = $this->getElection(); + + $vote_ranking = $oneVote->getContextualRankingWithoutSort($election); + $voteWeight = $oneVote->getWeight($election); + + $vote_candidate_list = []; + + foreach ($vote_ranking as $rank) { + foreach ($rank as $oneCandidate) { + $vote_candidate_list[] = $election->getCandidateKey($oneCandidate); + } + } + + $done_Candidates = []; + + foreach ($vote_ranking as $candidates_in_rank) { + $candidates_in_rank_keys = []; + + foreach ($candidates_in_rank as $candidate) { + $candidates_in_rank_keys[] = $election->getCandidateKey($candidate); + } + + foreach ($candidates_in_rank as $candidate) { + $candidate_key = $election->getCandidateKey($candidate); + + // Process + foreach ($vote_candidate_list as $opponent_candidate_key) { + if ($candidate_key !== $opponent_candidate_key) { + $opponent_in_rank = null; + + // Win & Lose + if (!\in_array(needle: $opponent_candidate_key, haystack: $done_Candidates, strict: true) && + !($opponent_in_rank = \in_array(needle: $opponent_candidate_key, haystack: $candidates_in_rank_keys, strict: true))) { + $pairwise[$candidate_key]['win'][$opponent_candidate_key] += $voteWeight; + $pairwise[$opponent_candidate_key]['lose'][$candidate_key] += $voteWeight; + + // Null + } elseif ($opponent_in_rank ?? \in_array(needle: $opponent_candidate_key, haystack: $candidates_in_rank_keys, strict: true)) { + $pairwise[$candidate_key]['null'][$opponent_candidate_key] += $voteWeight; + } + } + } + + $done_Candidates[] = $candidate_key; + } + } + } +} diff --git a/include/Condorcet/src/Algo/StatsVerbosity.php b/include/Condorcet/src/Algo/StatsVerbosity.php new file mode 100644 index 0000000..7850e1c --- /dev/null +++ b/include/Condorcet/src/Algo/StatsVerbosity.php @@ -0,0 +1,23 @@ + ($count - $length); $i--) { + $a = $a->multipliedBy($i); + } + + $b = BigInteger::of(1); + for ($i = $length; $i > 0; $i--) { + $b = $b->multipliedBy($i); + } + + $r = $a->dividedBy($b); + + try { + return $r->toInt(); + } catch (IntegerOverflowException $e) { + throw new CondorcetIntegerOverflowException($e->getMessage()); + } + } else { + $a = 1; + for ($i = $count; $i > ($count - $length); $i--) { + $a *= $i; + } + + $b = 1; + for ($i = $length; $i > 0; $i--) { + $b *= $i; + } + + if (\is_float($a) || \is_float($b)) { // @phpstan-ignore-line + throw new CondorcetIntegerOverflowException; + } else { + return (int) ($a / $b); + } + } + } + + public static function compute(array $values, int $length, array $append_before = []): SplFixedArray + { + $count = \count($values); + $r = new SplFixedArray(self::getPossibleCountOfCombinations($count, $length)); + + $arrKey = 0; + foreach (self::computeGenerator($values, $length, $append_before) as $oneCombination) { + $r[$arrKey++] = $oneCombination; + } + + return $r; + } + + public static function computeGenerator(array $values, int $length, array $append_before = []): \Generator + { + $count = \count($values); + $size = 2 ** $count; + $keys = array_keys($values); + + for ($i = 0; $i < $size; $i++) { + $b = sprintf('%0' . $count . 'b', $i); + $out = []; + + for ($j = 0; $j < $count; $j++) { + if ($b[$j] === '1') { + $out[$keys[$j]] = $values[$keys[$j]]; + } + } + + if (\count($out) === $length) { + yield array_values(array_merge($append_before, $out)); + } + } + } +} diff --git a/include/Condorcet/src/Algo/Tools/PairwiseStats.php b/include/Condorcet/src/Algo/Tools/PairwiseStats.php new file mode 100644 index 0000000..8f2c322 --- /dev/null +++ b/include/Condorcet/src/Algo/Tools/PairwiseStats.php @@ -0,0 +1,71 @@ + $candidate_data) { + $comparison[$candidate_key]['win'] = 0; + $comparison[$candidate_key]['null'] = 0; + $comparison[$candidate_key]['lose'] = 0; + $comparison[$candidate_key]['balance'] = 0; + $comparison[$candidate_key]['sum_defeat_margin'] = 0; + $comparison[$candidate_key]['worst_pairwise_defeat_winning'] = 0; + $comparison[$candidate_key]['worst_pairwise_defeat_margin'] = null; + $comparison[$candidate_key]['worst_pairwise_opposition'] = 0; + + foreach ($candidate_data['win'] as $opponentKey => $opponentLose) { + $defeat_margin = $candidate_data['lose'][$opponentKey] - $opponentLose; + + // Worst margin defeat + if ($comparison[$candidate_key]['worst_pairwise_defeat_margin'] === null || $comparison[$candidate_key]['worst_pairwise_defeat_margin'] < $defeat_margin) { + $comparison[$candidate_key]['worst_pairwise_defeat_margin'] = $defeat_margin; + } + + // Worst pairwise opposition + if ($comparison[$candidate_key]['worst_pairwise_opposition'] < $candidate_data['lose'][$opponentKey]) { + $comparison[$candidate_key]['worst_pairwise_opposition'] = $candidate_data['lose'][$opponentKey]; + } + + + // for each Win, null, Lose + if ($opponentLose > $candidate_data['lose'][$opponentKey]) { + $comparison[$candidate_key]['win']++; + $comparison[$candidate_key]['balance']++; + } elseif ($opponentLose === $candidate_data['lose'][$opponentKey]) { + $comparison[$candidate_key]['null']++; + } else { + $comparison[$candidate_key]['lose']++; + $comparison[$candidate_key]['balance']--; + + $comparison[$candidate_key]['sum_defeat_margin'] += $defeat_margin; + + // Worst winning defeat + if ($comparison[$candidate_key]['worst_pairwise_defeat_winning'] < $candidate_data['lose'][$opponentKey]) { + $comparison[$candidate_key]['worst_pairwise_defeat_winning'] = $candidate_data['lose'][$opponentKey]; + } + } + } + } + + return $comparison; + } +} diff --git a/include/Condorcet/src/Algo/Tools/Permutations.php b/include/Condorcet/src/Algo/Tools/Permutations.php new file mode 100644 index 0000000..43dcd53 --- /dev/null +++ b/include/Condorcet/src/Algo/Tools/Permutations.php @@ -0,0 +1,106 @@ +multipliedBy($candidatesNumber - $iteration); + } + + try { + return $result->toInt(); + } catch (IntegerOverflowException $e) { + throw new CondorcetIntegerOverflowException($e->getMessage()); + } + } else { + $result = $candidatesNumber; + + for ($iteration = 1; $iteration < $candidatesNumber; $iteration++) { + $result = $result * ($candidatesNumber - $iteration); + } + + if (\is_float($result)) { // @phpstan-ignore-line + throw new CondorcetIntegerOverflowException; + } else { + return $result; + } + } + } + + public function __construct(array $candidates) + { + $this->candidates = array_values($candidates); + } + + public function getResults(): SplFixedArray + { + $results = new SplFixedArray(self::getPossibleCountOfPermutations(\count($this->candidates))); + $arrKey = 0; + + foreach ($this->getPermutationGenerator() as $onePermutation) { + $results[$arrKey++] = $onePermutation; + } + + return $results; + } + + public function getPermutationGenerator(): \Generator + { + return $this->permutationGenerator($this->candidates); + } + + protected function permutationGenerator(array $elements): \Generator + { + if (\count($elements) <= 1) { + yield [1 => reset($elements)]; // Set the only key to index 1 + } else { + foreach ($this->permutationGenerator(\array_slice($elements, 1)) as $permutation) { + foreach (range(0, \count($elements) - 1) as $i) { + $r = array_merge( + \array_slice($permutation, 0, $i), + [$elements[0]], + \array_slice($permutation, $i) + ); + + // Set first key to 1 + $r = [null, ...$r]; + unset($r[0]); + + yield $r; + } + } + } + } +} diff --git a/include/Condorcet/src/Algo/Tools/StvQuotas.php b/include/Condorcet/src/Algo/Tools/StvQuotas.php new file mode 100644 index 0000000..ebcaa76 --- /dev/null +++ b/include/Condorcet/src/Algo/Tools/StvQuotas.php @@ -0,0 +1,57 @@ + self::DROOP, + 'hare quota', 'hare' => self::HARE, + 'hagenbach-bischoff quota', 'hagenbach-bischoff' => self::HAGENBACH_BISCHOFF, + 'imperiali quota', 'imperiali' => self::IMPERIALI, + }; + } catch (\UnhandledMatchError $e) { + throw new StvQuotaNotImplementedException('"'.$quota.'"'); + } + } + + public function getQuota(int $votesWeight, int $seats): float + { + return match ($this) { + self::DROOP => floor(($votesWeight / ($seats + 1)) + 1), + self::HARE => $votesWeight / $seats, + self::HAGENBACH_BISCHOFF => $votesWeight / ($seats + 1), + self::IMPERIALI, => $votesWeight / ($seats + 2), + }; + } +} diff --git a/include/Condorcet/src/Algo/Tools/TieBreakersCollection.php b/include/Condorcet/src/Algo/Tools/TieBreakersCollection.php new file mode 100644 index 0000000..37e9641 --- /dev/null +++ b/include/Condorcet/src/Algo/Tools/TieBreakersCollection.php @@ -0,0 +1,74 @@ +getPairwise(); + $pairwiseStats = PairwiseStats::PairwiseComparison($pairwise); + $tooKeep = []; + + foreach ($candidatesKeys as $oneCandidateKeyTotest) { + $select = true; + foreach ($candidatesKeys as $oneChallengerKey) { + if ($oneCandidateKeyTotest === $oneChallengerKey) { + continue; + } + + if ($pairwise[$oneCandidateKeyTotest]['win'][$oneChallengerKey] > $pairwise[$oneCandidateKeyTotest]['lose'][$oneChallengerKey] || + $pairwiseStats[$oneCandidateKeyTotest]['balance'] > $pairwiseStats[$oneChallengerKey]['balance'] || + $pairwiseStats[$oneCandidateKeyTotest]['win'] > $pairwiseStats[$oneChallengerKey]['win'] + ) { + $select = false; + } + } + + if ($select) { + $tooKeep[] = $oneCandidateKeyTotest; + } + } + + return (\count($tooKeep) > 0) ? $tooKeep : $candidatesKeys; + } + + public static function tieBreakerWithAnotherMethods(Election $election, array $methods, array $candidatesKeys): array + { + foreach ($methods as $oneMethod) { + $tooKeep = []; + + $methodResults = $election->getResult($oneMethod)->getResultAsInternalKey(); + + foreach ($methodResults as $rankValue) { + foreach ($rankValue as $oneCandidateKey) { + if (\in_array($oneCandidateKey, $candidatesKeys, true)) { + $tooKeep[] = $oneCandidateKey; + } + } + + if (\count($tooKeep) > 0 && \count($tooKeep) !== \count($candidatesKeys)) { + return $tooKeep; + } + } + } + + return $candidatesKeys; + } +} diff --git a/include/Condorcet/src/Algo/Tools/VirtualVote.php b/include/Condorcet/src/Algo/Tools/VirtualVote.php new file mode 100644 index 0000000..51e2f02 --- /dev/null +++ b/include/Condorcet/src/Algo/Tools/VirtualVote.php @@ -0,0 +1,28 @@ +removeCandidate($oneCandidate); + } + + return $virtualVote; + } +} diff --git a/include/Condorcet/src/Candidate.php b/include/Condorcet/src/Candidate.php new file mode 100644 index 0000000..8bfbb4b --- /dev/null +++ b/include/Condorcet/src/Candidate.php @@ -0,0 +1,149 @@ +setName($name); + } + + public function __toString(): string + { + return $this->getName(); + } + + public function __serialize(): array + { + $this->link = null; + + $r = get_object_vars($this); + unset($r['link']); + + return $r; + } + + // ------- + + // SETTERS + + #[PublicAPI] + #[Description("Change the candidate name.\n*If this will not cause conflicts if the candidate is already participating in elections and would namesake. This situation will throw an exception.*")] + #[FunctionReturn('In case of success, return TRUE')] + #[Throws(CandidateInvalidNameException::class)] + public function setName( + #[FunctionParameter('Candidate Name')] + string $name + ): bool { + $name = trim($name); + + if (mb_strlen($name) > Election::MAX_CANDIDATE_NAME_LENGTH) { + throw new CandidateInvalidNameException($name); + } + + if (preg_match('/<|>|\n|\t|\0|\^|\*|\$|:|;|(\|\|)|"|#/', $name) === 1) { + throw new CandidateInvalidNameException($name); + } + + if (!$this->checkNameInElectionContext($name)) { + throw new CandidateExistsException("the name '{$name}' is taken by another candidate"); + } + + $this->name[] = ['name' => $name, 'timestamp' => microtime(true)]; + + return true; + } + + public function setProvisionalState(bool $provisional): void + { + $this->provisional = $provisional; + } + + // GETTERS + + #[PublicAPI] + #[Description('Get the candidate name.')] + #[FunctionReturn('Candidate name.')] + #[Related('Candidate::getHistory', 'Candidate::setName')] + public function getName(): string + { + return end($this->name)['name']; + } + + #[PublicAPI] + #[Description('Return an history of each namming change, with timestamp.')] + #[FunctionReturn('An explicit multi-dimenssional array.')] + #[Related('Candidate::getCreateTimestamp')] + public function getHistory(): array + { + return $this->name; + } + + #[PublicAPI] + #[Description('Get the timestamp corresponding of the creation of this candidate.')] + #[FunctionReturn('Timestamp')] + #[Related('Candidate::getTimestamp')] + public function getCreateTimestamp(): float + { + return $this->name[0]['timestamp']; + } + + #[PublicAPI] + #[Description('Get the timestamp corresponding of the last namming change.')] + #[FunctionReturn('Timestamp')] + #[Related('Candidate::getCreateTimestamp')] + public function getTimestamp(): float + { + return end($this->name)['timestamp']; + } + + #[PublicAPI] + #[Description("When you create yourself the vote object, without use the Election::addVote or other native election method. And if you use string input (or array of string).\nThen, these string input will be converted to into temporary candidate objects, named \"provisional\". because you don't create the candidate yourself. They have a provisonal statut true.\nWhen the vote will be added for the first time to an election, provisional candidate object with a name that matches an election candidate, will be converted into the election candidate. And first ranking will be save into Vote history (Vote::getHistory).\n\nSee VoteTest::testVoteHistory() test for a demonstration. In principle this is transparent from a usage point of view. If you want to avoid any non-strict comparisons, however, you should prefer to create your votes with the Election object, or with Candidate Objects in input. But, you must never getback a candidate marked as provisional in an another election in the same time, it's will not working.")] + #[FunctionReturn('True if candidate object is in a provisional state, false else.')] + public function getProvisionalState(): bool + { + return $this->provisional; + } + + // ------- + + // INTERNAL + + private function checkNameInElectionContext(string $name): bool + { + foreach ($this->getLinks() as $link => $value) { + if (!$link->canAddCandidate($name)) { + return false; + } + } + + return true; + } +} diff --git a/include/Condorcet/src/Condorcet.php b/include/Condorcet/src/Condorcet.php new file mode 100644 index 0000000..6e5ea5c --- /dev/null +++ b/include/Condorcet/src/Condorcet.php @@ -0,0 +1,236 @@ + (Algo\Methods\CondorcetBasic::class)::METHOD_NAME]; + + public static bool $UseTimer = false; + + + /////////// STATICS METHODS /////////// + + // Return library version number + #[PublicAPI] + #[Description('Get the library version.')] + #[FunctionReturn('Condorcet PHP version.')] + #[Related('Election::getObjectVersion')] + public static function getVersion( + #[FunctionParameter("* true will return : '2.0'\n* false will return : '2.0.0'")] + bool $major = false + ): string { + if ($major === true) { + $version = explode('.', self::VERSION); + return $version[0].'.'.$version[1]; + } else { + return self::VERSION; + } + } + + // Return an array with auth methods + #[PublicAPI] + #[Description('Get a list of supported algorithm.')] + #[FunctionReturn('Populated by method string name. You can use it on getResult ... and others methods.')] + #[Related('static Condorcet::isAuthMethod', 'static Condorcet::getMethodClass')] + public static function getAuthMethods( + #[FunctionParameter('Include or not the natural Condorcet base algorithm')] + bool $basic = false + ): array { + $auth = self::$authMethods; + + // Don't show Natural Condorcet + if (!$basic) { + unset($auth[self::CONDORCET_BASIC_CLASS]); + } + + return array_column($auth, 0); + } + + + // Return the Class default method + #[PublicAPI] + #[Description('Return the Condorcet static default method.')] + #[FunctionReturn('Method name.')] + #[Related('static Condorcet::getAuthMethods', 'static Condorcet::setDefaultMethod')] + public static function getDefaultMethod(): ?string + { + return self::$defaultMethod; + } + + + // Check if the method is supported + #[PublicAPI] + #[Description('Return the full class path for a method.')] + #[FunctionReturn('Return null is method not exist.')] + #[Throws(AlgorithmException::class)] + #[Related('static Condorcet::getAuthMethods')] + public static function getMethodClass( + #[FunctionParameter('A valid method name')] + string $method + ): ?string { + $auth = self::$authMethods; + + if (empty($method)) { + throw new AlgorithmException('no method name given'); + } + + if (isset($auth[$method])) { + return $method; + } else { // Alias + foreach ($auth as $class => $alias) { + foreach ($alias as $entry) { + if (strcasecmp($method, $entry) === 0) { + return $class; + } + } + } + } + + return null; + } + + #[PublicAPI] + #[Description('Test if a method is in the result set of Condorcet::getAuthMethods.')] + #[FunctionReturn('True / False')] + #[Related('static Condorcet::getMethodClass', 'static Condorcet::getAuthMethods')] + public static function isAuthMethod( + #[FunctionParameter('A valid method name or class')] + string $method + ): bool { + return self::getMethodClass($method) !== null; + } + + + // Add algos + #[PublicAPI] + #[Description('If you create your own Condorcet Algo. You will need it !')] + #[FunctionReturn('True on Success. False on failure.')] + #[Related('static Condorcet::isAuthMethod', 'static Condorcet::getMethodClass')] + public static function addMethod( + #[FunctionParameter('The class name implementing your method. The class name includes the namespace it was declared in (e.g. Foo\Bar).')] + string $methodClass + ): bool { + // Check algos + if (self::isAuthMethod($methodClass) || !self::testMethod($methodClass)) { + return false; + } + + // Adding algo + self::$authMethods[$methodClass] = $methodClass::METHOD_NAME; + + if (self::getDefaultMethod() === null) { + self::setDefaultMethod($methodClass); + } + + return true; + } + + + // Check if the class Algo. exist and ready to be used + protected static function testMethod(string $method): bool + { + if (!class_exists($method)) { + throw new AlgorithmException("no class found for '{$method}'"); + } + + if (!is_subclass_of($method, Algo\MethodInterface::class) || !is_subclass_of($method, Algo\Method::class)) { + throw new AlgorithmException('the given class is not correct'); + } + + foreach ($method::METHOD_NAME as $alias) { + if (self::isAuthMethod($alias)) { + throw new AlgorithmException('the given class is using an existing alias'); + } + } + + return true; + } + + + // Change default method for this class. + #[PublicAPI] + #[Description('Put a new static method by default for the news Condorcet objects.')] + #[FunctionReturn('In case of success, return TRUE')] + #[Related('static Condorcet::getDefaultMethod')] + public static function setDefaultMethod( + #[FunctionParameter('A valid method name or class')] + string $method + ): bool { + if (($method = self::getMethodClass($method)) && $method !== self::CONDORCET_BASIC_CLASS) { + self::$defaultMethod = $method; + return true; + } else { + return false; + } + } + + public static function condorcetBasicSubstitution(?string $substitution): string + { + if ($substitution !== null) { + if (self::isAuthMethod($substitution)) { + $algo = $substitution; + } else { + throw new AlgorithmException("No class found for method '{$substitution}'"); + } + } else { + $algo = self::CONDORCET_BASIC_CLASS; + } + + return $algo; + } +} diff --git a/include/Condorcet/src/CondorcetVersion.php b/include/Condorcet/src/CondorcetVersion.php new file mode 100644 index 0000000..75597d7 --- /dev/null +++ b/include/Condorcet/src/CondorcetVersion.php @@ -0,0 +1,37 @@ +objectVersion); + return $version[0].'.'.$version[1]; + } else { + return $this->objectVersion; + } + } +} diff --git a/include/Condorcet/src/Console/Assets/Sources.md b/include/Condorcet/src/Console/Assets/Sources.md new file mode 100644 index 0000000..2b245a3 --- /dev/null +++ b/include/Condorcet/src/Console/Assets/Sources.md @@ -0,0 +1,3 @@ +* Logo 73c: https://patorjk.com/software/taag/#p=display&f=Slant&t=Condorcet%20PHP _(default)_ + +* Logos 90c && 125c: https://patorjk.com/software/taag/#p=display&h=1&f=Lean&t=Condorcet%20PHP _(fitted)_ \ No newline at end of file diff --git a/include/Condorcet/src/Console/Assets/logo.125c.ascii b/include/Condorcet/src/Console/Assets/logo.125c.ascii new file mode 100644 index 0000000..f963054 --- /dev/null +++ b/include/Condorcet/src/Console/Assets/logo.125c.ascii @@ -0,0 +1,7 @@ + _/_/_/ _/ _/ _/_/_/ _/ _/ _/_/_/ + _/ _/_/ _/_/_/ _/_/_/ _/_/ _/ _/_/ _/_/_/ _/_/ _/_/_/_/ _/ _/ _/ _/ _/ _/ + _/ _/ _/ _/ _/ _/ _/ _/ _/ _/_/ _/ _/_/_/_/ _/ _/_/_/ _/_/_/_/ _/_/_/ +_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ + _/_/_/ _/_/ _/ _/ _/_/_/ _/_/ _/ _/_/_/ _/_/_/ _/_/ _/ _/ _/ _/ + +========================================================================================== =============================== \ No newline at end of file diff --git a/include/Condorcet/src/Console/Assets/logo.73c.ascii b/include/Condorcet/src/Console/Assets/logo.73c.ascii new file mode 100644 index 0000000..60ba5c1 --- /dev/null +++ b/include/Condorcet/src/Console/Assets/logo.73c.ascii @@ -0,0 +1,5 @@ + ______ __ __ ____ __ ______ + / ____/___ ____ ____/ /___ _____________ / /_ / __ \/ / / / __ \ + / / / __ \/ __ \/ __ / __ \/ ___/ ___/ _ \/ __/ / /_/ / /_/ / /_/ / +/ /___/ /_/ / / / / /_/ / /_/ / / / /__/ __/ /_ / ____/ __ / ____/ +\____/\____/_/ /_/\__,_/\____/_/ \___/\___/\__/ /_/ /_/ /_/_/ \ No newline at end of file diff --git a/include/Condorcet/src/Console/Assets/logo.90c.ascii b/include/Condorcet/src/Console/Assets/logo.90c.ascii new file mode 100644 index 0000000..6d67d86 --- /dev/null +++ b/include/Condorcet/src/Console/Assets/logo.90c.ascii @@ -0,0 +1,7 @@ + _/_/_/ _/ _/ + _/ _/_/ _/_/_/ _/_/_/ _/_/ _/ _/_/ _/_/_/ _/_/ _/_/_/_/ + _/ _/ _/ _/ _/ _/ _/ _/ _/ _/_/ _/ _/_/_/_/ _/ +_/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ _/ + _/_/_/ _/_/ _/ _/ _/_/_/ _/_/ _/ _/_/_/ _/_/_/ _/_/ + +========================================================================================== \ No newline at end of file diff --git a/include/Condorcet/src/Console/Commands/ElectionCommand.php b/include/Condorcet/src/Console/Commands/ElectionCommand.php new file mode 100644 index 0000000..9fa42fb --- /dev/null +++ b/include/Condorcet/src/Console/Commands/ElectionCommand.php @@ -0,0 +1,803 @@ +setHelp('This command takes candidates and votes as input. The output is the result of that election.') + + ->addOption( + name: 'candidates', + shortcut: 'c', + mode: InputOption::VALUE_REQUIRED, + description: 'Candidates list file path or direct input', + ) + ->addOption( + name: 'votes', + shortcut: 'w', + mode: InputOption::VALUE_REQUIRED, + description: 'Votes list file path or direct input', + ) + ->addOption( + name: 'import-condorcet-election-format', + mode: InputOption::VALUE_REQUIRED, + description: 'File path. Setup an election and his data from a Condorcet Election file as defined as standard on https://github.com/CondorcetPHP/CondorcetElectionFormat . Other parameters from the command line argument have the priority if set. Other votes can be added with the --vote argument, other candidates can\'t be added.', + ) + ->addOption( + name: 'import-debian-format', + mode: InputOption::VALUE_REQUIRED, + description: 'File path. Setup an election and his data from a Debian tally file. Other votes can be added with the --vote argument, other candidates can\'t be added.', + ) + ->addOption( + name: 'import-david-hill-format', + mode: InputOption::VALUE_REQUIRED, + description: 'File path. Setup an election and his data from a Debian tally file. Other votes can be added with the --vote argument, other candidates can\'t be added.', + ) + + + ->addOption( + name: 'stats', + shortcut: 's', + mode: InputOption::VALUE_NONE, + description: 'Get detailed stats (equivalent to --show-pairwise and --method-stats)', + ) + ->addOption( + name: 'method-stats', + mode: InputOption::VALUE_NONE, + description: 'Get detailed stats per method', + ) + ->addOption( + name: 'show-pairwise', + shortcut: 'p', + mode: InputOption::VALUE_NONE, + description: 'Get pairwise computation', + ) + ->addOption( + name: 'list-votes', + shortcut: 'l', + mode: InputOption::VALUE_NONE, + description: 'List registered votes', + ) + ->addOption( + name: 'natural-condorcet', + shortcut: 'r', + mode: InputOption::VALUE_NONE, + description: 'Print natural Condorcet winner / loser', + ) + ->addOption( + name: 'deactivate-implicit-ranking', + shortcut: 'i', + mode: InputOption::VALUE_NONE, + description: 'Deactivate implicit ranking', + ) + ->addOption( + name: 'allows-votes-weight', + shortcut: 'g', + mode: InputOption::VALUE_NONE, + description: 'Allows vote weight', + ) + ->addOption( + name: 'no-tie', + shortcut: 't', + mode: InputOption::VALUE_NONE, + description: 'Add no-tie constraint for vote', + ) + + ->addOption( + name: 'seats', + mode: InputOption::VALUE_REQUIRED, + description: 'Specify the number of seats for proportional methods', + ) + ->addOption( + name: 'quota', + mode: InputOption::VALUE_REQUIRED, + description: 'Quota to be used for STV compatible methods', + ) + + ->addOption( + name: 'deactivate-file-cache', + mode: InputOption::VALUE_NONE, + description: "Don't use a disk cache for very large elections. Forces to work exclusively in RAM.", + ) + ->addOption( + name: 'votes-per-mb', + mode: InputOption::VALUE_REQUIRED, + description: 'Adjust memory in case of failure. Default is 100. Try to lower it.', + ) + + ->addArgument( + name: 'methods', + mode: InputArgument::OPTIONAL | InputArgument::IS_ARRAY, + description: 'Methods to output', + ) + ; + } + + public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void + { + if ($input->mustSuggestOptionValuesFor('quota')) { + $r = []; + + foreach (StvQuotas::cases() as $case) { + $r[] = $case->name; + } + + $suggestions->suggestValues($r); + } elseif ($input->mustSuggestArgumentValuesFor('methods')) { + $suggestions->suggestValues(Condorcet::getAuthMethods()); + } + } + + protected function initialize(InputInterface $input, OutputInterface $output): void + { + // Initialize Style & Terminal + $this->io = new CondorcetStyle($input, $output); + $this->terminal = new Terminal; + + // Setup Timer Manager + $this->timer = new Manager; + + // Setup Memory + if ($input->getOption('votes-per-mb') && ($NewVotesPerMB = (int) $input->getOption('votes-per-mb')) >= 1) { + self::$VotesPerMB = $NewVotesPerMB; + } + + // Setup Election Object + $this->election = new Election; + $this->displayMethodsStats = $input->getOption('method-stats') || $input->getOption('stats'); + $this->election->setStatsVerbosity($this->displayMethodsStats ? StatsVerbosity::HIGH : StatsVerbosity::NONE); + + // Parameters + $this->setUpParameters($input); + $this->iniMemoryLimit = (self::$forceIniMemoryLimitTo === null) ? preg_replace('`[^-0-9KMG]`', '', \ini_get('memory_limit')) : self::$forceIniMemoryLimitTo; + + // Non-interactive candidates + $this->candidates = $input->getOption('candidates') ?? null; + + // Non-interactive votes + $this->votes = $input->getOption('votes') ?? null; + + $this->CondorcetElectionFormatPath = $input->getOption('import-condorcet-election-format') ?? null; + $this->DebianFormatPath = $input->getOption('import-debian-format') ?? null; + $this->DavidHillFormatPath = $input->getOption('import-david-hill-format') ?? null; + + // Logo + $this->io->newLine(); + $this->io->logo($this->terminal->getWidth()); + $this->io->newLine(); + + // Header + $this->io->version(); + $this->io->inlineSeparator(); + $this->io->author(Condorcet::AUTHOR); + $this->io->inlineSeparator(); + $this->io->homepage(Condorcet::HOMEPAGE); + $this->io->newLine(2); + } + + protected function interact(InputInterface $input, OutputInterface $output): void + { + if (empty($this->CondorcetElectionFormatPath) && empty($this->DebianFormatPath) && empty($this->DavidHillFormatPath)) { + // Interactive Candidates + if (empty($this->candidates)) { + $this->io->title('Enter the candidates'); + $this->io->instruction('Candidates', 'Enter each candidate names'); + + $registeringCandidates = []; + + while (true) { + $answer = $this->io->ask('Please register candidate N°'.(\count($registeringCandidates) + 1).' (or press enter to continue)'); + + if ($answer === null) { + break; + } else { + array_push($registeringCandidates, ...explode(';', $answer)); + } + } + + $this->candidates = implode(';', $registeringCandidates); + } + + // Interactive Votes + if (empty($this->votes)) { + $this->io->title('Enter the votes'); + $this->io->instruction('Format', 'Candidate B > CandidateName D > CandidateName C = CandidateName A'); + + $registeringVotes = []; + + while (true) { + $answer = $this->io->ask('Please register vote N°'.(\count($registeringVotes) + 1).' (or press enter to continue)'); + + if ($answer === null) { + break; + } else { + array_push($registeringVotes, ...explode(';', $answer)); + } + } + + $this->votes = implode(';', $registeringVotes); + } + + // Interactive Methods + if (empty($input->getArgument('methods'))) { + $this->io->title('Enter the methods'); + $this->io->instruction('Voting methods', 'Choose by entering their numbers separated by commas. Press enter for the default method.'); + + $c = 0; + $registeringMethods = []; + + $authMehods = Condorcet::getAuthMethods(); + $authMehods = array_merge(['ALL'], $authMehods); + + $registeringMethods = $this->io->choiceMultiple('Select methods', $authMehods, Condorcet::getDefaultMethod()::METHOD_NAME[0], true); + + $input->setArgument('methods', $registeringMethods); + } + + if (empty($input->getOption('seats'))) { + $hasProportionalMethods = false; + $methods = FormaterHelper::prepareMethods($input->getArgument('methods')); + + foreach ($methods as $oneMethod) { + if ($oneMethod['class']::IS_PROPORTIONAL) { + $hasProportionalMethods = true; + break; + } + } + + if ($hasProportionalMethods) { + $this->io->instruction('Number of Seats', 'Some of the method(s) chosen are proportional and require a number of seats.'); + + $answer = $this->io->ask('Number of seats to fill', (string) 100, static function ($answer): string { + if (!is_numeric($answer)) { + throw new CondorcetInternalException('Seats must be numeric'); + } + + if (($answer = (int) $answer) < 1) { + throw new CondorcetInternalException('Seats must be a natural number (positive)'); + } + + return (string) $answer; + }); + + $input->setOption('seats', $answer); + $this->election->setNumberOfSeats((int) $answer); + } + } + } + } + + protected function importInputsData(InputInterface $input): void + { + // Define Callback + $callBack = $this->useDataHandler($input); + + // Setup Elections, candidates and votes from Condorcet Election Format + if ($input->getOption('import-condorcet-election-format')) { + $this->parseFromCondorcetElectionFormat($callBack); + $this->setUpParameters($input); # Do it again, because command line parameters have priority + } + + if ($input->getOption('import-debian-format')) { + $this->election->setNumberOfSeats(1); # Debian must be 1 + $this->parseFromDebianFormat(); + $this->setUpParameters($input); # Do it again, because command line parameters have priority + } + + if ($input->getOption('import-david-hill-format')) { + $this->parseFromDavidHillFormat(); + $this->setUpParameters($input); # Do it again, because command line parameters have priority + } + + // Parse Votes & Candidates from classicals inputs + !empty($this->candidates) && $this->parseFromCandidatesArguments(); + !empty($this->votes) && $this->parseFromVotesArguments($callBack); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + // Start Timer Chrono + $chrono = new Chrono($this->timer, 'CondorcetConsoleCommand'); + + // Import Inputs + $this->importInputsData($input); + + // Inputs Display Section + $this->displayInputsSection(); + + // Debug Display Section + if ($output->isDebug()) { + $this->displayDebugSection(); + } + + // Configuration Display Section + $this->displayConfigurationSection(); + + // Verbose Display Section + if ($output->isVerbose() || $input->getOption('list-votes')) { + $this->displayDetailedElectionInputsSection($input, $output); + } + + // Pairwise Display Section + if ($input->getOption('show-pairwise') || $input->getOption('stats')) { + $this->displayPairwiseSection($output); + $this->io->newLine(); + } + + // NaturalCondorcet Dislay Section + $this->displayNaturalCondorcet($input, $output); + + // MethodsResults Display Section + $this->io->newLine(); + $this->io->title('Results per methods'); + $this->displayMethodsResultSection($input, $output); + + + // RM Sqlite Database if exist + /** + * @infection-ignore-all + */ + if (($SQLitePath = $this->SQLitePath) !== null) { + $this->election = null; + unlink($SQLitePath); + } + + // Timer + unset($chrono); + $this->displayTimerSection(); + + return Command::SUCCESS; + } + + protected function displayInputsSection(): void + { + $messageCandidates = "{$this->election->countCandidates()} candidate".($this->election->countCandidates() > 1 ? 's' : '').' registered'; + $messageVotes = ''.number_format($this->election->countVotes(), thousands_separator: ' ').' vote'.($this->election->countVotes() > 1 ? 's' : '').' registered'; + + $this->io->writeln(''.str_repeat('-', mb_strlen($messageCandidates.$messageVotes) + 4 - 24).''); + $this->io->write($messageCandidates); + $this->io->inlineSeparator(); + $this->io->writeln($messageVotes); + $this->io->newLine(); + } + + protected function displayDebugSection(): void + { + $this->io->newLine(); + $this->io->title('Debug - External Handler Informations'); + + $this->io->definitionList( + ['Ini max_memory' => $this->iniMemoryLimit], + ['Votes per Mb' => self::$VotesPerMB], + ['Db is used' => (empty($this->SQLitePath)) ? 'no' : 'yes, using path: '.$this->SQLitePath], + ['Max Votes in Memory' => $this->maxVotesInMemory], + ); + } + + protected function displayConfigurationSection(): void + { + $this->io->title('Configuration'); + + $this->io->definitionList( + ['Is vote weight allowed?' => $this->election->isVoteWeightAllowed() ? 'TRUE' : 'FALSE'], + new TableSeparator, + ['Votes are evaluated according to the implicit ranking rule?' => $this->election->getImplicitRankingRule() ? 'TRUE' : 'FALSE'], + new TableSeparator, + ['Is vote tie in rank allowed?' => \in_array(needle: NoTie::class, haystack: $this->election->getConstraints(), strict: true) ? 'FALSE' : 'TRUE'] + ); + } + + protected function displayDetailedElectionInputsSection(InputInterface $input, OutputInterface $output): void + { + $this->io->title('Detailed Election Inputs'); + + if ($output->isVerbose()) { + $this->displayVerbose($output); + } + + // List-Votes Display Section + if ($input->getOption('list-votes')) { + $this->displayVotesCount($output); + $this->io->newLine(); + $this->displayVotesList($output); + $this->io->newLine(); + } + } + + protected function displayVerbose(OutputInterface $output): void + { + $this->displayCandidatesList($output); + $this->io->newLine(); + $this->displayVotesCount($output); + $this->io->newLine(); + } + + protected function displayCandidatesList(OutputInterface $output): void + { + if (!$this->candidatesListIsWrite) { + // Candidate List + ($candidateTable = new Table($output)) + ->setHeaderTitle('Registered candidates') + ->setHeaders(['Num', 'Candidate name']) + + ->setStyle($this->io->MainTableStyle) + ->setColumnStyle(0, $this->io->FirstColumnStyle) + ->setColumnWidth(0, 14) + ; + + $candidate_num = 1; + foreach ($this->election->getCandidatesListAsString() as $oneCandidate) { + $candidateTable->addRow([$candidate_num++, $oneCandidate]); + } + + $candidateTable->render(); + + $this->candidatesListIsWrite = true; + } + } + + protected function displayVotesCount(OutputInterface $output): void + { + if (!$this->votesCountIsWrite) { + // Votes Count + ($votesStatsTable = new Table($output)) + ->setHeaderTitle('Stats - votes registration') + ->setHeaders(['Stats', 'Value']) + ->setColumnStyle(0, (new Tablestyle)->setPadType(\STR_PAD_LEFT)) + ->setStyle($this->io->MainTableStyle) + ; + + $formatter = static fn (int $input): string => number_format($input, thousands_separator: ' '); + + $votesStatsTable->addRow(['Count registered votes', $formatter($this->election->countVotes())]); + $votesStatsTable->addRow(['Count valid registered votes with constraints', $formatter($this->election->countValidVoteWithConstraints())]); + $votesStatsTable->addRow(['Count invalid registered votes with constraints', $formatter($this->election->countInvalidVoteWithConstraints())]); + $votesStatsTable->addRow(['Sum vote weight', $formatter($this->election->sumVotesWeight())]); + $votesStatsTable->addRow(['Sum valid votes weight with constraints', $formatter($this->election->sumValidVotesWeightWithConstraints())]); + + $votesStatsTable->render(); + + $this->votesCountIsWrite = true; + } + } + + protected function displayVotesList(OutputInterface $output): void + { + ($votesTable = new Table($output)) + ->setHeaderTitle('Registered Votes List') + ->setHeaders(['Vote Num', 'Vote', 'Weight', 'Vote Tags']) + + ->setColumnWidth(0, 8) + ->setColumnWidth(1, 30) + ->setColumnMaxWidth(2, 6) + ->setStyle($this->io->MainTableStyle) + ; + + foreach ($this->election->getVotesValidUnderConstraintGenerator() as $voteKey => $oneVote) { + $votesTable->addRow([($voteKey + 1), $oneVote->getSimpleRanking($this->election, false), $oneVote->getWeight($this->election), implode(',', $oneVote->getTags())]); + } + + $votesTable->render(); + } + + protected function displayPairwiseSection(OutputInterface $output): void + { + if (!$this->pairwiseIsWrite) { + (new Table($output)) + ->setHeaderTitle('Pairwise') + ->setHeaders(['For each candidate, show their win, null, or lose']) + ->setRows([[preg_replace('#!!float (\d+)#', '\1.0', Yaml::dump($this->election->getExplicitPairwise(), 100))]]) + + ->setStyle($this->io->MainTableStyle) + ->render() + ; + + $this->pairwiseIsWrite= true; + } + } + + protected function displayNaturalCondorcet(InputInterface $input, OutputInterface $output): void + { + if ($input->getOption('natural-condorcet')) { + $this->io->methodResultSection('Condorcet natural winner & loser'); + $this->io->newLine(); + + (new Table($output)) + ->setHeaderTitle('Natural Condorcet') + ->setHeaders(['Type', 'Candidate']) + ->setRows([ + [CondorcetStyle::CONDORCET_WINNER_SYMBOL_FORMATED.' Condorcet Winner', (string) ($this->election->getCondorcetWinner() ?? '-')], + [CondorcetStyle::CONDORCET_LOSER_SYMBOL_FORMATED.' Condorcet Loser', (string) ($this->election->getCondorcetLoser() ?? '-')], + ]) + + ->setStyle($this->io->MainTableStyle) + ->render() + ; + + $this->io->newLine(); + } + } + + protected function displayMethodsResultSection(InputInterface $input, OutputInterface $output): void + { + $methods = FormaterHelper::prepareMethods($input->getArgument('methods')); + + foreach ($methods as $oneMethod) { + $this->io->methodResultSection($oneMethod['name']); + + if (isset($oneMethod['class']::$optionQuota) && $input->getOption('quota') !== null) { + $this->election->setMethodOption($oneMethod['class'], 'Quota', StvQuotas::make($input->getOption('quota'))); // @phpstan-ignore-line + } + + // Result + $result = $this->election->getResult($oneMethod['name']); + + if (!empty($options = $result->getMethodOptions())) { + $rows = []; + foreach ($options as $key => $value) { + if ($value instanceof \BackedEnum) { + $value = $value->value; + } elseif (\is_array($value)) { + $value = implode(' / ', $value); + } + + $rows[] = [$key.':', $value]; + } + + $this->io->newLine(); + + (new Table($output)) + ->setHeaderTitle('Configuration: '.$oneMethod['name']) + ->setHeaders(['Variable', 'Value']) + ->setRows($rows) + + ->setColumnStyle(0, $this->io->FirstColumnStyle) + ->setColumnMaxWidth(0, 30) + + ->setColumnMaxWidth(0, 40) + + ->setStyle($this->io->MainTableStyle) + ->render() + ; + } + + $this->io->newLine(); + + $this->io->write(''.CondorcetStyle::CONDORCET_WINNER_SYMBOL_FORMATED.' Condorcet Winner'); + $this->io->inlineSeparator(); + $this->io->writeln(''.CondorcetStyle::CONDORCET_LOSER_SYMBOL_FORMATED.' Condorcet Loser'); + + (new Table($output)) + ->setHeaderTitle('Results: '.$oneMethod['name']) + ->setHeaders(['Rank', 'Candidates']) + ->setRows(FormaterHelper::formatResultTable($result)) + + ->setColumnWidth(0, $fcw = 20) + ->setColumnWidth(1, 50) + ->setColumnMaxWidth(1, ($this->terminal->getWidth() - $fcw - 10 - 3)) + + ->setColumnStyle(0, $this->io->FirstColumnStyle) + ->setStyle($this->io->MainTableStyle) + ->render() + ; + + // Stats + if ($this->displayMethodsStats) { + $table = (new Table($output)) + ->setHeaderTitle('Stats: '.$oneMethod['name']) + + ->setColumnWidth(0, 73) + ->setColumnMaxWidth(0, $this->terminal->getWidth() - 10) + // ->setColumnStyle(0, $this->io->FirstColumnStyle) + ->setStyle($this->io->MainTableStyle) + ; + + $line = 0; + foreach ($result->getStats() as $oneStatKey => $oneStatEntry) { + ++$line !== 1 && $table->addRow(new TableSeparator); + $table->addRow([preg_replace('#!!float (\d+)#', '\1.0', Yaml::dump([$oneStatKey => $oneStatEntry], 100))]); + } + + $table->render(); + } + } + } + + protected function displayTimerSection(): void + { + $executionTime = round($this->timer->getGlobalTimer(), 4); + + $this->io->newLine(); + $this->io->writeln(''.str_repeat('-', 23).'', OutputInterface::VERBOSITY_VERBOSE); + $this->io->writeln("Execution Time: {$executionTime}s", OutputInterface::VERBOSITY_VERBOSE); + $this->io->newLine(); + } + + # Processing + + protected function setUpParameters(InputInterface $input): void + { + /// Implicit Ranking + if ($input->getOption('deactivate-implicit-ranking')) { + $this->election->setImplicitRanking(false); + } + + // Allow Votes Weight + if ($input->getOption('allows-votes-weight')) { + $this->election->allowsVoteWeight(true); + } + + if ($input->getOption('seats') && ($seats = (int) $input->getOption('seats')) >= 1) { + $this->election->setNumberOfSeats($seats); + } + + try { + if ($input->getOption('no-tie')) { + $this->election->addConstraint(NoTie::class); + } + } catch (VoteConstraintException $e) { + str_contains($e->getMessage(), 'class is already registered') || throw $e; + } + } + + protected function parseFromCandidatesArguments(): void + { + if ($file = CommandInputHelper::getFilePath($this->candidates)) { + $this->election->parseCandidates($file, true); + } else { + $this->election->parseCandidates($this->candidates); + } + } + + protected function parseFromVotesArguments(\Closure $callBack): void + { + // Parses Votes + if ($file = CommandInputHelper::getFilePath($this->votes)) { + $this->election->parseVotesWithoutFail(input: $file, isFile: true, callBack: $callBack); + } else { + $this->election->parseVotesWithoutFail(input: $this->votes, isFile: false, callBack: $callBack); + } + } + + protected function parseFromCondorcetElectionFormat(\Closure $callBack): void + { + $file = CommandInputHelper::getFilePath($this->CondorcetElectionFormatPath); + + if ($file !== null) { + (new CondorcetElectionFormat($file))->setDataToAnElection($this->election, $callBack); + } else { + throw new FileDoesNotExistException('File does not exist, path: '.$this->CondorcetElectionFormatPath); + } + } + + protected function parseFromDebianFormat(): void + { + $file = CommandInputHelper::getFilePath($this->DebianFormatPath); + + if ($file !== null) { + (new DebianFormat($file))->setDataToAnElection($this->election); + } else { + throw new FileDoesNotExistException('File does not exist, path: '.$this->CondorcetElectionFormatPath); + } + } + + protected function parseFromDavidHillFormat(): void + { + $file = CommandInputHelper::getFilePath($this->DavidHillFormatPath); + + if ($file !== null) { + (new DavidHillFormat($file))->setDataToAnElection($this->election); + } else { + throw new FileDoesNotExistException('File does not exist, path: '.$this->CondorcetElectionFormatPath); + } + } + + protected function useDataHandler(InputInterface $input): ?\Closure + { + if ($input->getOption('deactivate-file-cache') || !class_exists('\PDO') || !\in_array(needle: 'sqlite', haystack: \PDO::getAvailableDrivers(), strict: true)) { + return null; + } else { + if ($this->iniMemoryLimit === '-1') { + $memoryLimit = 8 * (1000 * 1048576); # Limit to 8GB, use a true memory limit to go further + } else { + $memoryLimit = (int) preg_replace('`[^0-9]`', '', $this->iniMemoryLimit); + $memoryLimit *= match (mb_strtoupper(mb_substr($this->iniMemoryLimit, -1, 1))) { + 'K' => 1024, + 'M' => 1048576, + 'G' => (1000 * 1048576), + default => 1 + }; + } + + $memoryLimit = (int) ($memoryLimit / 1048576); + $this->maxVotesInMemory = self::$VotesPerMB * $memoryLimit; + + $callBack = function (int $inserted_votes_count): bool { + if ($inserted_votes_count > $this->maxVotesInMemory) { + + /** + * @infection-ignore-all + */ + if (file_exists($this->SQLitePath = getcwd().\DIRECTORY_SEPARATOR.'condorcet-bdd.sqlite')) { + unlink($this->SQLitePath); + } + + /** + * @infection-ignore-all + */ + $this->election->setExternalDataHandler(new PdoHandlerDriver(new \PDO('sqlite:'.$this->SQLitePath, '', '', [\PDO::ATTR_PERSISTENT => false]), true)); + + return false; // No, stop next iteration + } else { + return true; // Yes, continue + } + }; + + return $callBack; + } + } +} diff --git a/include/Condorcet/src/Console/CondorcetApplication.php b/include/Condorcet/src/Console/CondorcetApplication.php new file mode 100644 index 0000000..54fc5ac --- /dev/null +++ b/include/Condorcet/src/Console/CondorcetApplication.php @@ -0,0 +1,97 @@ +run(); + } + + public static function create(): bool + { + // New App + self::$SymfonyConsoleApplication = new SymfonyConsoleApplication('Condorcet', Condorcet::getVersion()); + + // Election command + $command = new ElectionCommand; + self::$SymfonyConsoleApplication->add($command); + self::$SymfonyConsoleApplication->setDefaultCommand($command->getName(), false); + + return true; + } + + public static function getVersionWithGitParsing(): string + { + $git = static function (string $path): string { + if (!is_dir($path . \DIRECTORY_SEPARATOR . '.git')) { + throw new NoGitShellException('Path is not valid'); + } + + $process = proc_open( + 'git describe --tags --match="v[0-9]*\.[0-9]*\.[0-9]*"', + [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], + $pipes, + $path + ); + + if (!\is_resource($process)) { + throw new NoGitShellException; + } + + $result = trim(stream_get_contents($pipes[1])); + + fclose($pipes[1]); + fclose($pipes[2]); + + $returnCode = proc_close($process); + + if ($returnCode !== 0) { + throw new NoGitShellException; + } + + return $result; + }; + + $applicationOfficialVersion = Condorcet::getVersion(); + + try { + $version = $git(__DIR__.'/../../'); + $commit = explode('-', $version)[2]; + + $match = []; + preg_match('/^v([0-9]+\.[0-9]+\.[0-9]+)/', $version, $match); + $gitLastestTag = $match[1]; + + $version = (version_compare($gitLastestTag, $applicationOfficialVersion, '>=')) ? $version : $applicationOfficialVersion.'-(dev)-'.$commit; + } catch (NoGitShellException) { // Git no available, use the Condorcet Version + $version = $applicationOfficialVersion; + } + + return $version; + } +} diff --git a/include/Condorcet/src/Console/Helper/CommandInputHelper.php b/include/Condorcet/src/Console/Helper/CommandInputHelper.php new file mode 100644 index 0000000..b50de97 --- /dev/null +++ b/include/Condorcet/src/Console/Helper/CommandInputHelper.php @@ -0,0 +1,31 @@ + 3 && ctype_alpha($path[0]) && $path[1] === ':' && strspn($path, '/\\', 2, 1))); + } +} diff --git a/include/Condorcet/src/Console/Helper/FormaterHelper.php b/include/Condorcet/src/Console/Helper/FormaterHelper.php new file mode 100644 index 0000000..291437a --- /dev/null +++ b/include/Condorcet/src/Console/Helper/FormaterHelper.php @@ -0,0 +1,67 @@ +getResultAsArray(true); + + foreach ($resultArray as $rank => &$line) { + if (\is_array($line)) { + $line = implode(',', $line); + } + + if ($rank === 1 && \count($result[1]) === 1 && $result[1][0] === $result->getCondorcetWinner()) { + $line .= ' '.CondorcetStyle::CONDORCET_WINNER_SYMBOL_FORMATED; + } elseif ($rank === max(array_keys($resultArray)) && \count($result[max(array_keys($resultArray))]) === 1 && $result[max(array_keys($resultArray))][0] === $result->getCondorcetLoser()) { + $line .= ' '.CondorcetStyle::CONDORCET_LOSER_SYMBOL_FORMATED; + } + + $line = [$rank, $line]; + } + + return $resultArray; + } + + public static function prepareMethods(array $methodArgument): array + { + if (empty($methodArgument)) { + return [['name' => Condorcet::getDefaultMethod()::METHOD_NAME[0], 'class' => Condorcet::getDefaultMethod()]]; + } else { + $methods = []; + + foreach ($methodArgument as $oneMethod) { + if (mb_strtolower($oneMethod) === 'all') { + $methods = Condorcet::getAuthMethods(false); + $methods = array_map(static fn ($m) => ['name' => $m, 'class' => Condorcet::getMethodClass($m)], $methods); + break; + } + + if (Condorcet::isAuthMethod($oneMethod)) { + $method_class = Condorcet::getMethodClass($oneMethod); + $method_name = $method_class::METHOD_NAME[0]; + + if (!\in_array(needle: $method_name, haystack: $methods, strict: true)) { + $methods[] = ['name' => $method_name, 'class' => $method_class]; + } + } + } + + return $methods; + } + } +} diff --git a/include/Condorcet/src/Console/Style/CondorcetStyle.php b/include/Condorcet/src/Console/Style/CondorcetStyle.php new file mode 100644 index 0000000..c51e4d4 --- /dev/null +++ b/include/Condorcet/src/Console/Style/CondorcetStyle.php @@ -0,0 +1,163 @@ +'.self::CONDORCET_WINNER_SYMBOL.''; + public const CONDORCET_LOSER_SYMBOL_FORMATED = ''.self::CONDORCET_LOSER_SYMBOL.''; + + public readonly TableStyle $MainTableStyle; + public readonly TableStyle $FirstColumnStyle; + + public function __construct(InputInterface $input, OutputInterface $output) + { + parent::__construct($input, $output); + + $output->getFormatter()->setStyle('condor1', new OutputFormatterStyle(foreground: self::CONDORCET_MAIN_COLOR)); + $output->getFormatter()->setStyle('condor2', new OutputFormatterStyle(foreground: self::CONDORCET_SECONDARY_COLOR)); + $output->getFormatter()->setStyle('condor3', new OutputFormatterStyle(foreground: self::CONDORCET_THIRD_COLOR)); + + $output->getFormatter()->setStyle('condor1b', new OutputFormatterStyle(foreground: self::CONDORCET_MAIN_COLOR, options: ['bold'])); + $output->getFormatter()->setStyle('condor2b', new OutputFormatterStyle(foreground: self::CONDORCET_SECONDARY_COLOR, options: ['bold'])); + $output->getFormatter()->setStyle('condor3b', new OutputFormatterStyle(foreground: self::CONDORCET_THIRD_COLOR, options: ['bold'])); + + $output->getFormatter()->setStyle('continue', new OutputFormatterStyle(foreground: '#008080')); + $output->getFormatter()->setStyle('comment', new OutputFormatterStyle(foreground: '#fed5b7')); + + $this->MainTableStyle = (new TableStyle) + ->setBorderFormat('%s') + ->setHeaderTitleFormat(' %s ') + ->setCellHeaderFormat('%s') + ->setCellRowFormat('%s') + ; + + $this->FirstColumnStyle = (new TableStyle) + ->setPadType(\STR_PAD_BOTH) + // ->setCellRowFormat('%s') # Buggy + ; + } + + public function logo(int $terminalSize): void + { + $path = __DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'Assets'.\DIRECTORY_SEPARATOR; + + if ($terminalSize >= 125) { + $path .= 'logo.125c.ascii'; + } elseif ($terminalSize >= 90) { + $path .= 'logo.90c.ascii'; + } else { + $path .= 'logo.73c.ascii'; + } + + $this->writeln(file_get_contents($path)); + } + + public function author(string $author): void + { + $this->write("Author: {$author}"); + } + + public function choiceMultiple(string $question, array $choices, mixed $default = null, bool $multi): mixed + { + if ($default !== null) { + $values = array_flip($choices); + $default = $values[$default] ?? $default; + } + + $questionChoice = new ChoiceQuestion($question, $choices, $default); + $questionChoice->setMultiselect($multi); + + return $this->askQuestion($questionChoice); + } + + public function homepage(string $homepage): void + { + $this->write("Homepage: {$homepage}"); + } + + public function inlineSeparator(): void + { + $this->write(' || '); + } + + public function instruction(string $prefix, string $message): void + { + $this->writeln("{$prefix}: {$message}"); + } + + public function section(string $message): void + { + $this->block( + messages: $message, + type: null, + style: 'fg='.self::CONDORCET_MAIN_COLOR.';bg='.self::CONDORCET_SECONDARY_COLOR.';options=bold', + padding: false + ); + } + + public function methodResultSection(string $message): void + { + $messageLength = mb_strlen($message) + 4; + $prefixLength = 15; + $totalLength = $messageLength + $prefixLength; + $totalBorderLength = $totalLength + 4; + + $horizontalBorder = ''.str_repeat('=', (int) $totalBorderLength).''; + $vbs = '|'; + + $spaceMessage = ''.str_repeat(' ', $messageLength).''; + $spacePrefix = ''.str_repeat(' ', $prefixLength).''; + $bande = "{$vbs} ".$spacePrefix.$spaceMessage." {$vbs}"; + + $this->writeln($horizontalBorder); + $this->writeln($bande); + $this->writeln("{$vbs} Vote Method {$message} {$vbs}"); + $this->writeln($bande); + $this->writeln($horizontalBorder); + } + + public function note(string|array $message): void + { + $this->block(messages: $message, style: 'fg=gray'); + } + + public function success(string|array $message): void + { + $this->block( + messages: $message, + type: 'OK', + style: 'fg='.self::CONDORCET_MAIN_COLOR.';bg='.self::CONDORCET_SECONDARY_COLOR.';options=bold', + padding: true + ); + } + + public function version(): void + { + $version = CondorcetApplication::getVersionWithGitParsing(); + $this->write("Version: {$version}"); + } +} diff --git a/include/Condorcet/src/Constraints/NoTie.php b/include/Condorcet/src/Constraints/NoTie.php new file mode 100644 index 0000000..6cd4ecf --- /dev/null +++ b/include/Condorcet/src/Constraints/NoTie.php @@ -0,0 +1,30 @@ +getContextualRankingWithoutSort($election); + + foreach ($voteRanking as $oneRank) { + if (\count($oneRank) > 1) { + return false; + } + } + + return true; + } +} diff --git a/include/Condorcet/src/DataManager/ArrayManager.php b/include/Condorcet/src/DataManager/ArrayManager.php new file mode 100644 index 0000000..36f2e9c --- /dev/null +++ b/include/Condorcet/src/DataManager/ArrayManager.php @@ -0,0 +1,415 @@ +setElection($election); + } + + public function __destruct() + { + $this->regularize(); + } + + public function __serialize(): array + { + $this->regularize(); + $this->clearCache(); + $this->rewind(); + + return ['Container' => $this->Container, 'DataHandler' => $this->DataHandler]; + } + + public function __unserialize(array $data): void + { + $this->Container = $data['Container']; + $this->DataHandler = $data['DataHandler']; + + $this->resetMaxKey(); + $this->resetCounter(); + } + + public function getElection(): Election + { + return $this->Election->get(); + } + + public function setElection(Election $election): void + { + $this->Election = \WeakReference::create($election); + } + + + /////////// Implement ArrayAccess /////////// + + public function offsetSet(mixed $offset, mixed $value): void + { + if ($offset === null) { + $this->Container[++$this->maxKey] = $value; + ++$this->counter; + } else { + $state = $this->keyExist($offset); + $this->Container[$offset] = $value; + + if (!$state) { + ++$this->counter; + + if ($offset > $this->maxKey) { + $this->maxKey = $offset; + } + + ksort($this->Container, \SORT_NUMERIC); + } elseif ($this->DataHandler !== null) { + $this->DataHandler->deleteOneEntity(key: $offset, justTry: true); + unset($this->Cache[$offset]); + } + + $this->clearCache(); + } + + // Delegate this step to VoteManager. To give him time to calculate the pairwise iteratively. Without recharging the memory. + // $this->checkRegularize(); + } + + // Use by isset() function, must return false if offset value is null. + public function offsetExists(mixed $offset): bool + { + return isset($this->Container[$offset]) || ($this->DataHandler !== null && $this->DataHandler->selectOneEntity(key: $offset) !== false); + } + + public function offsetUnset(mixed $offset): void + { + if ($this->keyExist($offset)) { + if (\array_key_exists(key: $offset, array: $this->Container)) { + $this->preDeletedTask($this->Container[$offset]); + unset($this->Container[$offset]); + } else { + if (\array_key_exists(key: $offset, array: $this->Cache)) { + $this->preDeletedTask($this->Cache[$offset]); + unset($this->Cache[$offset]); + } + + $this->DataHandler->deleteOneEntity(key: $offset, justTry: false); + } + + --$this->counter; + } + } + + public function offsetGet(mixed $offset): mixed + { + if (isset($this->Container[$offset])) { + return $this->Container[$offset]; + } elseif ($this->DataHandler !== null) { + if (\array_key_exists(key: $offset, array: $this->Cache)) { + return $this->Cache[$offset]; + } else { + $oneEntity = $this->DataHandler->selectOneEntity(key: $offset); + if ($oneEntity === false) { + return null; + } else { + return $this->Cache[$offset] = $this->decodeOneEntity($oneEntity); + } + } + } else { + return null; + } + } + + + /////////// Implement Iterator /////////// + + protected bool $valid = true; + + public function rewind(): void + { + $this->cursor = null; + $this->valid = true; + + reset($this->Cache); + reset($this->Container); + } + + public function current(): mixed + { + return $this->offsetGet($this->key()); + } + + public function key(): ?int + { + if ($this->counter === 0) { + return null; + } else { + return $this->cursor ?? $this->getFirstKey(); + } + } + + public function next(): void + { + $oldCursor = $this->cursor; + + if ($this->cursor >= $this->maxKey) { + // Do nothing + } elseif (!$this->isUsingHandler()) { + $this->setCursorOnNextKeyInArray($this->Container); + } else { + $this->populateCache(); + $this->setCursorOnNextKeyInArray($this->Cache); + } + + if ($this->cursor === $oldCursor) { + $this->valid = false; + } + } + + protected function setCursorOnNextKeyInArray(array &$array): void + { + next($array); + $arrayKey = key($array); + + if ($arrayKey > $this->key()) { + $this->cursor = $arrayKey; + } + } + + public function valid(): bool + { + return ($this->counter !== 0) ? $this->valid : false; + } + + + /////////// Implement Countable /////////// + + public function count(): int + { + return $this->counter; + } + + /////////// Array Methods /////////// + + public function getFullDataSet(): array + { + if ($this->isUsingHandler()) { + $this->regularize(); + $this->clearCache(); + + return $this->Cache = $this->decodeManyEntities($this->DataHandler->selectRangeEntities(key: 0, limit: $this->maxKey + 1)); + } else { + return $this->Container; + } + } + + public function keyExist(int $offset): bool + { + if (\array_key_exists(key: $offset, array: $this->Container) || ($this->DataHandler !== null && $this->DataHandler->selectOneEntity(key: $offset) !== false)) { + return true; + } else { + return false; + } + } + + public function getFirstKey(): int + { + $r = array_keys($this->Container); + + if ($this->DataHandler !== null) { + $r[] = $this->DataHandler->selectMinKey(); + } + + return (int) min($r); + } + + public function getContainerSize(): int + { + return \count($this->Container); + } + + public function getCacheSize(): int + { + return \count($this->Cache); + } + + public function debugGetCache(): array + { + return $this->Cache; + } + + + /////////// HANDLER API /////////// + + abstract protected function preDeletedTask(Vote $object): void; + + abstract protected function decodeOneEntity(string $data): Vote; + + abstract protected function encodeOneEntity(Vote $data): string; + + protected function decodeManyEntities(array $entities): array + { + $r = []; + + foreach ($entities as $key => $oneEntity) { + $r[(int) $key] = $this->decodeOneEntity($oneEntity); + } + + return $r; + } + + protected function encodeManyEntities(array $entities): array + { + $r = []; + + foreach ($entities as $key => $oneEntity) { + $r[(int) $key] = $this->encodeOneEntity($oneEntity); + } + + return $r; + } + + public function regularize(): bool + { + if (!$this->isUsingHandler() || empty($this->Container)) { + return false; + } else { + $this->DataHandler->insertEntities($this->encodeManyEntities($this->Container)); + $this->Container = []; + return true; + } + } + + public function checkRegularize(): bool + { + if ($this->DataHandler !== null && self::$MaxContainerLength <= $this->getContainerSize()) { + $this->regularize(); + return true; + } else { + return false; + } + } + + protected function populateCache(): void + { + $this->regularize(); + + $currentKey = $this->key(); + + if (empty($this->Cache) || $currentKey >= $this->CacheMaxKey || $currentKey < $this->CacheMinKey) { + $this->clearCache(); + $this->Cache = $this->decodeManyEntities($this->DataHandler->selectRangeEntities(key: $currentKey, limit: self::$CacheSize)); + + $keys = array_keys($this->Cache); + $this->CacheMaxKey = max($keys); + $this->CacheMinKey = min($keys); + } + } + + public function clearCache(): void + { + foreach ($this->Cache as $e) { + $this->preDeletedTask($e); + } + + $this->Cache = []; + $this->CacheMaxKey = 0; + $this->CacheMinKey = 0; + } + + public function isUsingHandler(): bool + { + return $this->DataHandler !== null; + } + + /////////// HANDLER INTERRACTION /////////// + + public function resetCounter(): int + { + return $this->counter = $this->getContainerSize() + ($this->isUsingHandler() ? $this->DataHandler->countEntities() : 0); + } + + public function resetMaxKey(): ?int + { + $this->resetCounter(); + + if ($this->count() < 1) { + $this->maxKey = -1; + return null; + } else { + $maxContainerKey = empty($this->Container) ? null : max(array_keys($this->Container)); + $maxHandlerKey = $this->DataHandler !== null ? $this->DataHandler->selectMaxKey() : null; + + return $this->maxKey = max($maxContainerKey, $maxHandlerKey); + } + } + + #[Throws(DataHandlerException::class)] + public function importHandler(DataHandlerDriverInterface $handler): bool + { + if ($handler->countEntities() === 0) { + $this->DataHandler = $handler; + + try { + $this->regularize(); + } catch (\Exception $e) { + $this->DataHandler = null; + $this->resetCounter(); + $this->resetMaxKey(); + throw $e; + } + + $this->resetCounter(); + $this->resetMaxKey(); + + return true; + } else { + throw new DataHandlerException; + } + } + + public function closeHandler(): void + { + if ($this->DataHandler !== null) { + $this->regularize(); + $this->clearCache(); + + $this->Container = $this->decodeManyEntities($this->DataHandler->selectRangeEntities(key: 0, limit: $this->maxKey + 1)); + + $this->DataHandler = null; + + $this->resetCounter(); + $this->resetMaxKey(); + } + } +} diff --git a/include/Condorcet/src/DataManager/DataHandlerDrivers/DataHandlerDriverInterface.php b/include/Condorcet/src/DataManager/DataHandlerDrivers/DataHandlerDriverInterface.php new file mode 100644 index 0000000..b4bf3b0 --- /dev/null +++ b/include/Condorcet/src/DataManager/DataHandlerDrivers/DataHandlerDriverInterface.php @@ -0,0 +1,46 @@ +dataCallBack($EntityData) before returning stored data by the two select method. + + + // Entities to register. + // Ex: [Condorcet/Vote,Condorcet/Vote,Condorcet/Vote]. The key should not be kept + public function insertEntities(array $input): void; + + // Delete Entity with this key. If justTry is true, don't throw Exception if row not exist. Else throw an \CondorcetPHP\Concordet\Throwable\CondorcetInternalError. + public function deleteOneEntity(int $key, bool $justTry): ?int; + + // Return (int) max register key. + // SQL example : SELECT max(key) FROM... + public function selectMaxKey(): ?int; + + // Return (int) max register key. + // SQL example : SELECT min(key) FROM... + public function selectMinKey(): ?int; + + // Return (int) :nomber of recording + // SQL example : SELECT count(*) FROM... + public function countEntities(): int; + + // Return one Entity by key + public function selectOneEntity(int $key): string|bool; + + // Return an array of entity where $key is the first Entity and $limit is the maximum number of entity. Must return an array, keys must be preseve into there. + // Arg example : (42, 3) + // Return example : [42 => Condorcet/Vote, 43 => Condorcet/Vote, 44 => Condorcet/Vote] + public function selectRangeEntities(int $key, int $limit): array; +} diff --git a/include/Condorcet/src/DataManager/DataHandlerDrivers/PdoDriver/PdoHandlerDriver.php b/include/Condorcet/src/DataManager/DataHandlerDrivers/PdoDriver/PdoHandlerDriver.php new file mode 100644 index 0000000..f199307 --- /dev/null +++ b/include/Condorcet/src/DataManager/DataHandlerDrivers/PdoDriver/PdoHandlerDriver.php @@ -0,0 +1,306 @@ + 'Entities', 'primaryColumnName' => 'id', 'dataColumnName' => 'data']) + { + if (!$this->checkStructureTemplate($struct)) { + throw new DataHandlerException('invalid structure template for PdoHandler'); + } + + $this->struct = $struct; + + $this->handler = $bdd; + + $this->handler->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + if ($tryCreateTable) { + $this->createTable(); + } + + $this->initPrepareQuery(); + } + + // INTERNAL + + protected function checkStructureTemplate(array &$struct): bool + { + if (!empty($struct['tableName']) && !empty($struct['primaryColumnName']) && !empty($struct['dataColumnName']) && + \is_string($struct['tableName']) && \is_string($struct['primaryColumnName']) && \is_string($struct['dataColumnName']) + ) { + return true; + } else { + return false; + } + } + + public function createTable(): void + { + $dataType = (self::$preferBlobInsteadVarchar) ? 'BLOB' : 'VARCHAR'; + + $tableCreationQuery = match ($this->handler->getAttribute(\PDO::ATTR_DRIVER_NAME)) { + default => 'CREATE TABLE IF NOT EXISTS '.$this->struct['tableName'].' ('.$this->struct['primaryColumnName'].' INT AUTO_INCREMENT PRIMARY KEY NOT NULL , '.$this->struct['dataColumnName'].' '.$dataType.' NOT NULL );' + }; + + try { + $this->handler->exec($tableCreationQuery); + } catch (\Exception $e) { + throw $e; + } + } + + protected function initPrepareQuery(): void + { + $template = []; + + // Base - Small query ends + $template['end_template'] = ';'; + $template['insert_template'] = 'INSERT INTO '.$this->struct['tableName'].' ('.$this->struct['primaryColumnName'].', '.$this->struct['dataColumnName'].') VALUES '; + $template['delete_template'] = 'DELETE FROM '.$this->struct['tableName'].' WHERE '.$this->struct['primaryColumnName']; + $template['select_template'] = 'SELECT '.$this->struct['primaryColumnName'].','.$this->struct['dataColumnName'].' FROM '.$this->struct['tableName'].' WHERE '.$this->struct['primaryColumnName']; + + // Select the max / min key value. Usefull if array cursor is lost on DataManager. + $this->prepare['selectMaxKey'] = $this->handler->prepare('SELECT max('.$this->struct['primaryColumnName'].') FROM '.$this->struct['tableName'] . $template['end_template']); + $this->prepare['selectMinKey'] = $this->handler->prepare('SELECT min('.$this->struct['primaryColumnName'].') FROM '.$this->struct['tableName'] . $template['end_template']); + + // Insert many Entities + $makeMany = static function (int $how) use (&$template): string { + $query = $template['insert_template']; + + for ($i=1; $i < $how; $i++) { + $query .= '(:key'.$i.', :data'.$i.'),'; + } + + $query .= '(:key'.$how.', :data'.$how.')' . $template['end_template']; + + return $query; + }; + + foreach (self::SEGMENT as $value) { + $this->prepare['insert'.$value.'Entities'] = $this->handler->prepare($makeMany($value)); + } + + // Delete one Entity + $this->prepare['deleteOneEntity'] = $this->handler->prepare($template['delete_template'] . ' = ?' . $template['end_template']); + + // Get a Entity + $this->prepare['selectOneEntity'] = $this->handler->prepare($template['select_template'] . ' = ?' . $template['end_template']); + + // Get a range of Entity + $this->prepare['selectRangeEntities'] = $this->handler->prepare($template['select_template'] . ' >= :startKey order by '.$this->struct['primaryColumnName'].' asc LIMIT :limit' . $template['end_template']); + + // Count Entities + $this->prepare['countEntities'] = $this->handler->prepare('SELECT count('.$this->struct['primaryColumnName'].') FROM '. $this->struct['tableName'] . $template['end_template']); + } + + protected function initTransaction(): void + { + if (!$this->transaction) { + $this->transaction = $this->handler->beginTransaction(); + } + } + + public function closeTransaction(): void + { + if ($this->transaction === true) { + + /** + * @infection-ignore-all + */ + if ($this->queryError) { + throw new CondorcetInternalError('Query Error.'); + } + + $this->transaction = !$this->handler->commit(); + } + } + + + // DATA MANAGER + public function insertEntities(array $input): void + { + $this->sliceInput($input); + + try { + $this->initTransaction(); + + foreach ($input as $group) { + $param = []; + $i = 1; + $group_count = \count($group); + + foreach ($group as $key => &$Entity) { + $param['key'.$i] = $key; + $param['data'.$i++] = $Entity; + } + + $this->prepare['insert'.$group_count.'Entities']->execute( + $param + ); + + /** + * @infection-ignore-all + */ + if ($this->prepare['insert'.$group_count.'Entities']->rowCount() !== $group_count) { + throw new CondorcetInternalError('Not all entities have been inserted'); + } + + $this->prepare['insert'.$group_count.'Entities']->closeCursor(); + } + + $this->closeTransaction(); + } catch (\Throwable $e) { + $this->queryError = true; + throw $e; + } + } + + protected function sliceInput(array &$input): void + { + $count = \count($input); + + foreach (self::SEGMENT as $value) { + if ($count >= $value) { + $input = array_chunk($input, $value, true); + + $end = end($input); + if (\count($input) > 1 && \count($end) < $value) { + $this->sliceInput($end); + unset($input[key($input)]); + $input = array_merge($input, $end); + } + break; + } + } + } + + + public function deleteOneEntity(int $key, bool $justTry): ?int + { + try { + $this->prepare['deleteOneEntity']->bindParam(1, $key, \PDO::PARAM_INT); + $this->prepare['deleteOneEntity']->execute(); + + $deleteCount = $this->prepare['deleteOneEntity']->rowCount(); + + /** + * @infection-ignore-all + */ + if (!$justTry && $deleteCount !== 1) { + throw new CondorcetInternalError('Entity deletion failure.'); + } + + $this->prepare['deleteOneEntity']->closeCursor(); + + return $deleteCount; + } catch (\Throwable $e) { + $this->queryError = true; + throw $e; + } + } + + public function selectMaxKey(): ?int + { + if ($this->countEntities() === 0) { + return null; + } + + $this->prepare['selectMaxKey']->execute(); + $r = (int) $this->prepare['selectMaxKey']->fetch(\PDO::FETCH_NUM)[0]; + $this->prepare['selectMaxKey']->closeCursor(); + + return $r; + } + + public function selectMinKey(): int + { + $this->prepare['selectMinKey']->execute(); + $r = (int) $this->prepare['selectMinKey']->fetch(\PDO::FETCH_NUM)[0]; + $this->prepare['selectMinKey']->closeCursor(); + + return $r; + } + + public function countEntities(): int + { + $this->prepare['countEntities']->execute(); + $r = (int) $this->prepare['countEntities']->fetch(\PDO::FETCH_NUM)[0]; + $this->prepare['countEntities']->closeCursor(); + + return $r; + } + + // return false if Entity does not exist. + public function selectOneEntity(int $key): string|bool + { + $this->prepare['selectOneEntity']->bindParam(1, $key, \PDO::PARAM_INT); + $this->prepare['selectOneEntity']->execute(); + + $r = $this->prepare['selectOneEntity']->fetchAll(\PDO::FETCH_NUM); + $this->prepare['selectOneEntity']->closeCursor(); + if (!empty($r)) { + return $r[0][1]; + } else { + return false; + } + } + + public function selectRangeEntities(int $key, int $limit): array + { + $this->prepare['selectRangeEntities']->bindParam(':startKey', $key, \PDO::PARAM_INT); + $this->prepare['selectRangeEntities']->bindParam(':limit', $limit, \PDO::PARAM_INT); + $this->prepare['selectRangeEntities']->execute(); + + $r = $this->prepare['selectRangeEntities']->fetchAll(\PDO::FETCH_NUM); + $this->prepare['selectRangeEntities']->closeCursor(); + if (!empty($r)) { + $result = []; + foreach ($r as $value) { + $result[(int) $value[0]] = $value[1]; + } + + + return $result; + } else { + return []; + } + } +} diff --git a/include/Condorcet/src/DataManager/VotesManager.php b/include/Condorcet/src/DataManager/VotesManager.php new file mode 100644 index 0000000..e03e488 --- /dev/null +++ b/include/Condorcet/src/DataManager/VotesManager.php @@ -0,0 +1,271 @@ +registerLink($this->getElection()); + $vote->notUpdate = true; + $this->getElection()->checkVoteCandidate($vote); + $vote->notUpdate = false; + + return $vote; + } + + protected function encodeOneEntity(Vote $data): string + { + $data->destroyLink($this->getElection()); + + return str_replace([' > ', ' = '], ['>', '='], (string) $data); + } + + protected function preDeletedTask(Vote $object): void + { + $object->destroyLink($this->getElection()); + } + + /////////// Array Access - Specials improvements /////////// + + public function offsetGet(mixed $offset): Vote + { + return parent::offsetGet($offset); + } + + #[Throws(VoteManagerException::class)] + public function offsetSet(mixed $offset, mixed $value): void + { + if ($value instanceof Vote) { + parent::offsetSet((\is_int($offset) ? $offset : null), $value); + $this->UpdateAndResetComputing(key: $this->maxKey, type: 1); + } else { + throw new VoteManagerException; + } + + $this->checkRegularize(); + } + + public function offsetUnset(mixed $offset): void + { + $this->UpdateAndResetComputing(key: $offset, type: 2); + parent::offsetUnset($offset); + } + + /////////// Internal Election related methods /////////// + + public function UpdateAndResetComputing(int $key, int $type): void + { + $election = $this->getElection(); + + + if ($election->getState() === ElectionState::VOTES_REGISTRATION) { + if ($type === 1) { + $election->getPairwise()->addNewVote($key); + } elseif ($type === 2) { + $election->getPairwise()->removeVote($key); + } + + $election->cleanupCalculator(); + } else { + $election->setStateToVote(); + } + } + + + /////////// Get Votes Methods /////////// + + public function getVoteKey(Vote $vote): ?int + { + ($r = array_search(needle: $vote, haystack: $this->Container, strict: true)) !== false || ($r = array_search(needle: $vote, haystack: $this->Cache, strict: true)); + + return ($r !== false) ? $r : null; + } + + protected function getFullVotesListGenerator(): \Generator + { + foreach ($this as $voteKey => $vote) { + yield $voteKey => $vote; + } + } + + protected function getPartialVotesListGenerator(array $tags, bool $with): \Generator + { + foreach ($this as $voteKey => $vote) { + $noOne = true; + foreach ($tags as $oneTag) { + if (($oneTag === $voteKey) || \in_array(needle: $oneTag, haystack: $vote->getTags(), strict: true)) { + if ($with) { + yield $voteKey => $vote; + break; + } else { + $noOne = false; + } + } + } + + if (!$with && $noOne) { + yield $voteKey => $vote; + } + } + } + + // Get the votes list + public function getVotesList(?array $tags = null, bool $with = true): array + { + if ($tags === null) { + return $this->getFullDataSet(); + } else { + $search = []; + + foreach ($this->getPartialVotesListGenerator($tags, $with) as $voteKey => $vote) { + $search[$voteKey] = $vote; + } + + return $search; + } + } + + // Get the votes list as a generator object + public function getVotesListGenerator(?array $tags = null, bool $with = true): \Generator + { + if ($tags === null) { + return $this->getFullVotesListGenerator(); + } else { + return $this->getPartialVotesListGenerator($tags, $with); + } + } + + public function getVotesValidUnderConstraintGenerator(?array $tags = null, bool $with = true): \Generator + { + $election = $this->getElection(); + $generator = ($tags === null) ? $this->getFullVotesListGenerator() : $this->getPartialVotesListGenerator($tags, $with); + + foreach ($generator as $voteKey => $oneVote) { + if (!$election->testIfVoteIsValidUnderElectionConstraints($oneVote)) { + continue; + } + + yield $voteKey => $oneVote; + } + } + + public function getVotesListAsString(bool $withContext): string + { + $election = $this->getElection(); + + $simpleList = ''; + + $weight = []; + $nb = []; + + foreach ($this as $oneVote) { + $oneVoteString = $oneVote->getSimpleRanking($withContext ? $election : null); + + if (!\array_key_exists(key: $oneVoteString, array: $weight)) { + $weight[$oneVoteString] = 0; + } + if (!\array_key_exists(key: $oneVoteString, array: $nb)) { + $nb[$oneVoteString] = 0; + } + + if ($election->isVoteWeightAllowed()) { + $weight[$oneVoteString] += $oneVote->getWeight(); + } else { + $weight[$oneVoteString]++; + } + + $nb[$oneVoteString]++; + } + + ksort($weight, \SORT_NATURAL); + arsort($weight); + + $isFirst = true; + foreach ($weight as $key => $value) { + if (!$isFirst) { + $simpleList .= "\n"; + } + $voteString = ($key === '') ? CondorcetElectionFormat::SPECIAL_KEYWORD_EMPTY_RANKING : $key; + $simpleList .= $voteString.' * '.$nb[$key]; + $isFirst = false; + } + + return $simpleList; + } + + public function countVotes(?array $tag, bool $with): int + { + if ($tag === null) { + return \count($this); + } else { + $count = 0; + + foreach ($this as $key => $value) { + $noOne = true; + foreach ($tag as $oneTag) { + if (($oneTag === $key) || \in_array(needle: $oneTag, haystack: $value->getTags(), strict: true)) { + if ($with) { + $count++; + break; + } else { + $noOne = false; + } + } + } + + if (!$with && $noOne) { + $count++; + } + } + + return $count; + } + } + + public function countInvalidVoteWithConstraints(): int + { + $election = $this->getElection(); + $count = 0; + + foreach ($this as $oneVote) { + if (!$election->testIfVoteIsValidUnderElectionConstraints($oneVote)) { + $count++; + } + } + + return $count; + } + + public function sumVotesWeight(bool $constraint = false): int + { + $election = $this->getElection(); + $sum = 0; + + foreach ($this as $oneVote) { + if (!$constraint || $election->testIfVoteIsValidUnderElectionConstraints($oneVote)) { + $sum += $election->isVoteWeightAllowed() ? $oneVote->getWeight() : 1; + } + } + + return $sum; + } +} diff --git a/include/Condorcet/src/Election.php b/include/Condorcet/src/Election.php new file mode 100644 index 0000000..bd0fa03 --- /dev/null +++ b/include/Condorcet/src/Election.php @@ -0,0 +1,482 @@ +Candidates = []; + $this->Votes = new VotesManager($this); + $this->timer = new Timer_Manager; + } + + public function __serialize(): array + { + // Don't include others data + $include = [ + 'Candidates' => $this->Candidates, + 'Votes' => $this->Votes, + + 'State' => $this->State, + 'objectVersion' => $this->objectVersion, + 'AutomaticNewCandidateName' => $this->AutomaticNewCandidateName, + + 'ImplicitRanking' => $this->ImplicitRanking, + 'VoteWeightRule' => $this->VoteWeightRule, + 'Constraints' => $this->Constraints, + + 'Pairwise' => $this->Pairwise, + 'Calculator' => $this->Calculator, + ]; + + !self::$checksumMode && ($include += ['timer' => $this->timer]); + + return $include; + } + + public function __unserialize(array $data): void + { + // Only compare major and minor version numbers, not patch level + // e.g. 2.0 and 3.2 + $objectVersion = explode('.', $data['objectVersion']); + $objectVersion = $objectVersion[0] . '.' . $objectVersion[1]; + if (version_compare($objectVersion, Condorcet::getVersion(true), '!=')) { + throw new ElectionObjectVersionMismatchException($objectVersion); + } + + $this->Candidates = $data['Candidates']; + $this->Votes = $data['Votes']; + $this->Votes->setElection($this); + $this->registerAllLinks(); + + $this->AutomaticNewCandidateName = $data['AutomaticNewCandidateName']; + $this->State = $data['State']; + $this->objectVersion = $data['objectVersion']; + + $this->ImplicitRanking = $data['ImplicitRanking']; + $this->VoteWeightRule = $data['VoteWeightRule']; + $this->Constraints = $data['Constraints']; + + $this->Pairwise = $data['Pairwise']; + $this->Pairwise->setElection($this); + + $this->Calculator = $data['Calculator']; + foreach ($this->Calculator as $methodObject) { + $methodObject->setElection($this); + } + + $this->timer ??= $data['timer']; + } + + public function __clone(): void + { + $this->Votes = clone $this->Votes; + $this->Votes->setElection($this); + $this->registerAllLinks(); + + $this->timer = clone $this->timer; + + if ($this->Pairwise !== null) { + $this->Pairwise = clone $this->Pairwise; + $this->Pairwise->setElection($this); + } + } + + + /////////// TIMER & CHECKSUM /////////// + + #[PublicAPI] + #[Description('Returns the cumulated computation runtime of this object. Include only computation related methods.')] + #[FunctionReturn('(Float) Timer')] + #[Example('Manual - Timber benchmarking', 'https://github.com/julien-boudry/Condorcet/wiki/III-%23-A.-Avanced-features---Configuration-%23-1.-Timer-Benchmarking')] + #[Related('Election::getLastTimer')] + public function getGlobalTimer(): float + { + return $this->timer->getGlobalTimer(); + } + + #[PublicAPI] + #[Description('Return the last computation runtime (typically after a getResult() call.). Include only computation related methods.')] + #[FunctionReturn('(Float) Timer')] + #[Example('Manual - Timber benchmarking', 'https://github.com/julien-boudry/Condorcet/wiki/III-%23-A.-Avanced-features---Configuration-%23-1.-Timer-Benchmarking')] + #[Related('Election::getGlobalTimer')] + public function getLastTimer(): float + { + return $this->timer->getLastTimer(); + } + + #[PublicAPI] + #[Description('Get the Timer manager object.')] + #[FunctionReturn("An CondorcetPHP\Condorcet\Timer\Manager object using by this election.")] + #[Related('Election::getGlobalTimer', 'Election::getLastTimer')] + public function getTimerManager(): Timer_Manager + { + return $this->timer; + } + + #[PublicAPI] + #[Description("SHA-2 256 checksum of following internal data:\n* Candidates\n* Votes list & tags\n* Computed data (pairwise, algorithm cache, stats)\n* Class version (major version like 0.14)\n\nCan be powerfull to check integrity and security of an election. Or working with serialized object.")] + #[FunctionReturn('SHA-2 256 bits Hexadecimal')] + #[Example('Manual - Cryptographic Checksum', 'https://github.com/julien-boudry/Condorcet/wiki/III-%23-A.-Avanced-features---Configuration-%23-2.-Cryptographic-Checksum')] + public function getChecksum(): string + { + self::$checksumMode = true; + + $r = hash_init('sha256'); + + foreach ($this->Candidates as $value) { + hash_update($r, (string) $value); + } + + foreach ($this->Votes as $value) { + hash_update($r, (string) $value); + } + + $this->Pairwise !== null + && hash_update($r, serialize($this->Pairwise->getExplicitPairwise())); + + hash_update($r, $this->getObjectVersion(true)); + + self::$checksumMode = false; + + return hash_final($r); + } + + + /////////// LINKS REGULATION /////////// + + protected function registerAllLinks(): void + { + foreach ($this->Candidates as $value) { + $value->registerLink($this); + } + + foreach ($this->Votes as $value) { + $value->registerLink($this); + } + } + + + /////////// IMPLICIT RANKING & VOTE WEIGHT /////////// + + #[PublicAPI] + #[Description("Returns the corresponding setting as currently set (True by default).\nIf it is True then all votes expressing a partial ranking are understood as implicitly placing all the non-mentioned candidates exequos on a last rank.\nIf it is false, then the candidates not ranked, are not taken into account at all.")] + #[FunctionReturn('True / False')] + #[Related('Election::setImplicitRanking')] + public function getImplicitRankingRule(): bool + { + return $this->ImplicitRanking; + } + + #[PublicAPI] + #[Description("Set the setting and reset all result data.\nIf it is True then all votes expressing a partial ranking are understood as implicitly placing all the non-mentioned candidates exequos on a last rank.\nIf it is false, then the candidates not ranked, are not taken into account at all.")] + #[FunctionReturn('Return True')] + #[Related('Election::getImplicitRankingRule')] + public function setImplicitRanking( + #[FunctionParameter('New rule')] + bool $rule = true + ): bool { + $this->ImplicitRanking = $rule; + $this->cleanupCompute(); + return $this->getImplicitRankingRule(); + } + + #[PublicAPI] + #[Description("Returns the corresponding setting as currently set (False by default).\nIf it is True then votes vote optionally can use weight otherwise (if false) all votes will be evaluated as equal for this election.")] + #[FunctionReturn('True / False')] + #[Related('Election::allowsVoteWeight')] + public function isVoteWeightAllowed(): bool + { + return $this->VoteWeightRule; + } + + #[PublicAPI] + #[Description("Set the setting and reset all result data.\nThen the weight of votes (if specified) will be taken into account when calculating the results. Otherwise all votes will be considered equal.\nBy default, the voting weight is not activated and all votes are considered equal.")] + #[FunctionReturn('Return True')] + #[Related('Election::isVoteWeightAllowed')] + public function allowsVoteWeight( + #[FunctionParameter('New rule')] + bool $rule = true + ): bool { + $this->VoteWeightRule = $rule; + $this->cleanupCompute(); + return $this->isVoteWeightAllowed(); + } + + + /////////// VOTE CONSTRAINT /////////// + + #[PublicAPI] + #[Description('Add a constraint rules as a valid class path.')] + #[FunctionReturn('True on success.')] + #[Throws(VoteConstraintException::class)] + #[Example('Manual - Vote Constraints', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-5.-Vote-Constraints')] + #[Related('Election::getConstraints', 'Election::clearConstraints', 'Election::testIfVoteIsValidUnderElectionConstraints')] + public function addConstraint( + #[FunctionParameter('A valid class path. Class must extend VoteConstraint class')] + string $constraintClass + ): bool { + if (!class_exists($constraintClass)) { + throw new VoteConstraintException('class is not defined'); + } elseif (!is_subclass_of($constraintClass, VoteConstraintInterface::class)) { + throw new VoteConstraintException('class is not a valid subclass'); + } elseif (\in_array(needle: $constraintClass, haystack: $this->getConstraints(), strict: true)) { + throw new VoteConstraintException('class is already registered'); + } + + $this->cleanupCompute(); + + + $this->Constraints[] = $constraintClass; + + return true; + } + + #[PublicAPI] + #[Description('Get active constraints list.')] + #[FunctionReturn('Array with class name of each active constraint. Empty array if there is not.')] + #[Example('Manual - Vote Constraints', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-5.-Vote-Constraints')] + #[Related('Election::clearConstraints', 'Election::addConstraints', 'Election::testIfVoteIsValidUnderElectionConstraints')] + public function getConstraints(): array + { + return $this->Constraints; + } + + #[PublicAPI] + #[Description('Clear all constraints rules and clear previous results.')] + #[FunctionReturn('Return True.')] + #[Example('Manual - Vote Constraints', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-5.-Vote-Constraints')] + #[Related('Election::getConstraints', 'Election::addConstraints', 'Election::testIfVoteIsValidUnderElectionConstraints')] + public function clearConstraints(): bool + { + $this->Constraints = []; + + $this->cleanupCompute(); + + return true; + } + + #[PublicAPI] + #[Description('Test if a vote is valid with these election constraints.')] + #[FunctionReturn('Return True if vote will pass the constraints rules, else False.')] + #[Example('Manual - Vote Constraints', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-5.-Vote-Constraints')] + #[Related('Election::getConstraints', 'Election::addConstraints', 'Election::clearConstraints')] + public function testIfVoteIsValidUnderElectionConstraints( + #[FunctionParameter('A vote. Not necessarily registered in this election')] + Vote $vote + ): bool { + foreach ($this->Constraints as $oneConstraint) { + if ($oneConstraint::isVoteAllow($this, $vote) === false) { + return false; + } + } + + return true; + } + + + /////////// STV SEATS /////////// + + #[PublicAPI] + #[Description('Get number of Seats for STV methods.')] + #[FunctionReturn('Number of seats.')] + #[Related('Election::setNumberOfSeats', 'Result::getNumberOfSeats')] + public function getNumberOfSeats(): int + { + return $this->Seats; + } + + #[PublicAPI] + #[Description('Set number of Seats for STV methods.')] + #[FunctionReturn('Number of seats.')] + #[Throws(NoSeatsException::class)] + #[Related('Election::getNumberOfSeats')] + public function setNumberOfSeats( + #[FunctionParameter('The number of seats for proportional methods.')] + int $seats + ): int { + if ($seats > 0) { + $this->cleanupCompute(); + + $this->Seats = $seats; + } else { + throw new NoSeatsException; + } + + return $this->Seats; + } + + + /////////// LARGE ELECTION MODE /////////// + + #[PublicAPI] + #[Description('Import and enable an external driver to store vote on very large election.')] + #[FunctionReturn('True if success. Else throw an Exception.')] + #[Throws(DataHandlerException::class)] + #[Example('[Manual - DataHandler]', 'https://github.com/julien-boudry/Condorcet/blob/master/examples/specifics_examples/use_large_election_external_database_drivers.php')] + #[Related('Election::removeExternalDataHandler')] + public function setExternalDataHandler( + #[FunctionParameter('Driver object')] + DataHandlerDriverInterface $driver + ): bool { + if (!$this->Votes->isUsingHandler()) { + $this->Votes->importHandler($driver); + return true; + } else { + throw new DataHandlerException('external data handler cannot be imported'); + } + } + + #[PublicAPI] + #[Description('Remove an external driver to store vote on very large election. And import his data into classical memory.')] + #[FunctionReturn('True if success. Else throw an Exception.')] + #[Throws(DataHandlerException::class)] + #[Related('Election::setExternalDataHandler')] + public function removeExternalDataHandler(): bool + { + if ($this->Votes->isUsingHandler()) { + $this->Votes->closeHandler(); + return true; + } else { + throw new DataHandlerException('external data handler cannot be removed, is already in use'); + } + } + + + /////////// STATE /////////// + + #[PublicAPI] + #[Description('Get the election process level.')] + #[FunctionReturn("ElectionState::CANDIDATES_REGISTRATION: Candidate registered state. No votes, no result, no cache.\nElectionState::VOTES_REGISTRATION: Voting registration phase. Pairwise cache can exist thanks to dynamic computation if voting phase continue after the first get result. But method result never exist.\n3: Result phase: Some method result may exist, pairwise exist. An election will return to Phase 2 if votes are added or modified dynamically.")] + #[Related('Election::setStateToVote')] + public function getState(): ElectionState + { + return $this->State; + } + + // Close the candidate config, be ready for voting (optional) + #[PublicAPI] + #[Description("Force the election to get back to state 2. See Election::getState.\nIt is not necessary to use this method. The election knows how to manage its phase changes on its own. But it is a way to clear the cache containing the results of the methods.\n\nIf you are on state 1 (candidate registering), it's will close this state and prepare election to get firsts votes.\nIf you are on state 3. The method result cache will be clear, but not the pairwise. Which will continue to be updated dynamically.")] + #[FunctionReturn('Always True.')] + #[Throws(NoCandidatesException::class, ResultRequestedWithoutVotesException::class)] + #[Related('Election::getState')] + public function setStateToVote(): bool + { + if ($this->State === ElectionState::CANDIDATES_REGISTRATION) { + if (empty($this->Candidates)) { + throw new NoCandidatesException; + } + + $this->State = ElectionState::VOTES_REGISTRATION; + $this->preparePairwiseAndCleanCompute(); + } + + return true; + } + + // Prepare to compute results & caching system + protected function preparePairwiseAndCleanCompute(): bool + { + if ($this->Pairwise === null && $this->State === ElectionState::VOTES_REGISTRATION) { + $this->cleanupCompute(); + + // Do Pairwise + $this->makePairwise(); + + // Return + return true; + } elseif ($this->State === ElectionState::CANDIDATES_REGISTRATION || $this->countVotes() === 0) { + throw new ResultRequestedWithoutVotesException; + } else { + return false; + } + } +} diff --git a/include/Condorcet/src/ElectionProcess/CandidatesProcess.php b/include/Condorcet/src/ElectionProcess/CandidatesProcess.php new file mode 100644 index 0000000..d6799d3 --- /dev/null +++ b/include/Condorcet/src/ElectionProcess/CandidatesProcess.php @@ -0,0 +1,282 @@ +Candidates); + } + + #[PublicAPI] + #[Description('Return a list of registered candidates for this election.')] + #[FunctionReturn('List of candidates in an array.')] + #[Related('Election::countCandidates')] + public function getCandidatesList(): array + { + return $this->Candidates; + } + + // Get the list of registered CANDIDATES + #[PublicAPI] + #[Description('Return a list of registered candidates for this election.')] + #[FunctionReturn('List of candidates in an array populated with strings instead of CandidateObjects.')] + #[Related('Election::countCandidates')] + public function getCandidatesListAsString(): array + { + $result = []; + + foreach ($this->Candidates as $candidateKey => &$oneCandidate) { + $result[$candidateKey] = $oneCandidate->getName(); + } + + return $result; + } + + #[InternalModulesAPI] + public function getCandidateKey(Candidate|string $candidate): ?int + { + if ($candidate instanceof Candidate) { + $r = array_search(needle: $candidate, haystack: $this->Candidates, strict: true); + } else { + $r = array_search(needle: trim((string) $candidate), haystack: $this->Candidates, strict: false); + } + + return ($r !== false) ? $r : null; + } + + #[InternalModulesAPI] + public function getCandidateObjectFromKey(int $candidate_key): ?Candidate + { + return $this->Candidates[$candidate_key] ?? null; + } + + #[PublicAPI] + #[Description('Check if a candidate is already taking part in the election.')] + #[FunctionReturn('True / False')] + #[Related('Election::addCandidate')] + public function isRegisteredCandidate( + #[FunctionParameter('Candidate object or candidate string name. String name works only if the strict mode is active')] + Candidate|string $candidate, + #[FunctionParameter("Search comparison mode. In strict mode, candidate objects are compared strictly and a string input can't match anything.\nIf strict mode is false, the comparison will be based on name")] + bool $strictMode = true + ): bool { + return $strictMode ? \in_array(needle: $candidate, haystack: $this->Candidates, strict: true) : \in_array(needle: (string) $candidate, haystack: $this->Candidates, strict: false); + } + + #[PublicAPI] + #[Description('Find candidate object by string and return the candidate object.')] + #[FunctionReturn('Candidate object')] + public function getCandidateObjectFromName( + #[FunctionParameter('Candidate name')] + string $candidateName + ): ?Candidate { + foreach ($this->Candidates as $oneCandidate) { + if ($oneCandidate->getName() === $candidateName) { + return $oneCandidate; + } + } + + return null; + } + + + /////////// ADD & REMOVE CANDIDATE /////////// + + // Add a vote candidate before voting + #[PublicAPI] + #[Description('Add one candidate to an election.')] + #[FunctionReturn('The new candidate object (your or automatic one). Throws an exception on error (existing candidate...).')] + #[Throws(CandidateExistsException::class, VotingHasStartedException::class)] + #[Example('Manual - Manage Candidate', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates')] + #[Related('Election::parseCandidates', 'Election::addCandidatesFromJson', 'Election::removeCandidate', 'Election::getCandidatesList', 'Election::canAddCandidate')] + public function addCandidate( + #[FunctionParameter('Alphanumeric string or CondorcetPHP\Condorcet\Candidate object. The whitespace of your candidate name will be trimmed. If null, this function will create a new candidate with an automatic name.')] + Candidate|string|null $candidate = null + ): Candidate { + // only if the vote has not started + if ($this->State->value > ElectionState::CANDIDATES_REGISTRATION->value) { + throw new VotingHasStartedException("cannot add '{$candidate}'"); + } + + // Process + if (empty($candidate) && $candidate !== '0') { + while (!$this->canAddCandidate($this->AutomaticNewCandidateName)) { + $this->AutomaticNewCandidateName++; + } + + $newCandidate = new Candidate($this->AutomaticNewCandidateName); + } else { // Try to add the candidate_id + $newCandidate = ($candidate instanceof Candidate) ? $candidate : new Candidate((string) $candidate); + + if (!$this->canAddCandidate($newCandidate)) { + throw new CandidateExistsException((string) $candidate); + } + } + + // Register it + $this->Candidates[] = $newCandidate; + + // Linking + $newCandidate->registerLink($this); + + // Disallow other candidate object name matching + $newCandidate->setProvisionalState(false); + + return $newCandidate; + } + + #[PublicAPI] + #[Description('Check if a candidate is already registered. Uses strict Vote object comparison, but also string naming comparison in the election.')] + #[FunctionReturn('True if your candidate is available, false otherwise.')] + #[Related('Election::addCandidate')] + public function canAddCandidate( + #[FunctionParameter('String or Condorcet/Vote object')] + Candidate|string $candidate + ): bool { + return !$this->isRegisteredCandidate($candidate, false); + } + + // Destroy a register vote candidate before voting + #[PublicAPI] + #[Description("Remove candidates from an election.\n\n*Please note: You can't remove candidates after the first vote. An exception will be thrown.*")] + #[FunctionReturn("List of removed CondorcetPHP\Condorcet\Candidate object.")] + #[Throws(CandidateDoesNotExistException::class, VotingHasStartedException::class)] + #[Example('Manual - Manage Candidate', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates')] + #[Related('Election::addCandidate', 'Election::getCandidatesList')] + public function removeCandidates( + #[FunctionParameter("* String matching candidate name\n* CondorcetPHP\Condorcet\Candidate object\n* Array populated by CondorcetPHP\Condorcet\Candidate\n* Array populated by string matching candidate name")] + array|Candidate|string $candidates_input + ): array { + // only if the vote has not started + if ($this->State->value > ElectionState::CANDIDATES_REGISTRATION->value) { + throw new VotingHasStartedException; + } + + if (!\is_array($candidates_input)) { + $candidates_input = [$candidates_input]; + } + + foreach ($candidates_input as &$candidate) { + $candidate_key = $this->getCandidateKey($candidate); + + if ($candidate_key === null) { + throw new CandidateDoesNotExistException($candidate->getName()); + } + + $candidate = $candidate_key; + } + + $rem = []; + foreach ($candidates_input as $candidate_key) { + $this->Candidates[$candidate_key]->destroyLink($this); + + $rem[] = $this->Candidates[$candidate_key]; + + unset($this->Candidates[$candidate_key]); + } + + return $rem; + } + + + /////////// PARSE CANDIDATES /////////// + + #[PublicAPI] + #[Description('Import candidate from a JSON source.')] + #[FunctionReturn('List of newly registered candidate object.')] + #[Throws(CandidateExistsException::class)] + #[Example('Manual - Manage Candidates', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates')] + #[Related('Election::addCandidate', 'Election::parseCandidates', 'Election::addVotesFromJson')] + public function addCandidatesFromJson( + #[FunctionParameter('JSON string input')] + string $input + ): array { + $input = CondorcetUtil::prepareJson($input); + + // ------- + + $adding = []; + foreach ($input as $candidate) { + $candidate = new Candidate($candidate); + + if (!$this->canAddCandidate($candidate)) { + throw new CandidateExistsException((string) $candidate); + } + + $adding[] = $candidate; + } + + // Add Candidates + foreach ($adding as $oneCandidate) { + $this->addCandidate($oneCandidate); + } + + return $adding; + } + + #[PublicAPI] + #[Description('Import candidate from a text source.')] + #[FunctionReturn('List of newly registered candidate object. Count it for checking if all candidates have been correctly registered.')] + #[Throws(CandidateExistsException::class, VoteMaxNumberReachedException::class)] + #[Example('Manual - Manage Candidates', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-A.-Create-an-Election-%23-2.-Create-Candidates')] + #[Related('Election::addCandidate', 'Election::addCandidatesFromJson', 'Election::parseVotes')] + public function parseCandidates( + #[FunctionParameter('String or valid path to a text file')] + string $input, + #[FunctionParameter('If true, the input is evaluated as path to a text file')] + bool $isFile = false + ): array { + $input = CondorcetUtil::prepareParse($input, $isFile); + + $adding = []; + foreach ($input as $line) { + // addCandidate + if (self::$maxParseIteration !== null && \count($adding) >= self::$maxParseIteration) { + throw new VoteMaxNumberReachedException(self::$maxParseIteration); + } + + if (!$this->canAddCandidate($line)) { + throw new CandidateExistsException($line); + } + + $adding[] = $line; + } + + foreach ($adding as $oneNewCandidate) { + $this->addCandidate($oneNewCandidate); + } + + return $adding; + } +} diff --git a/include/Condorcet/src/ElectionProcess/ElectionState.php b/include/Condorcet/src/ElectionProcess/ElectionState.php new file mode 100644 index 0000000..1662888 --- /dev/null +++ b/include/Condorcet/src/ElectionProcess/ElectionState.php @@ -0,0 +1,19 @@ +timer, 'GetResult with filter') : null; + + $filter = new self; + + foreach ($this->getCandidatesList() as $candidate) { + $filter->addCandidate($candidate); + } + + foreach ($this->getVotesList(tags: $methodOptions['tags'], with: $methodOptions['withTag']) as $vote) { + $filter->addVote($vote); + } + + unset($chrono); + + return $filter->getResult($method); + } + + ////// Start ////// + + // Prepare + $this->preparePairwiseAndCleanCompute(); + + // ------- + + $chrono = (Condorcet::$UseTimer === true) ? new Timer_Chrono($this->timer) : null; + + if ($method === null) { + $this->initResult(Condorcet::getDefaultMethod()); + $result = $this->Calculator[Condorcet::getDefaultMethod()]->getResult(); + } elseif ($wanted_method = Condorcet::getMethodClass($method)) { + $this->initResult($wanted_method); + $result = $this->Calculator[$wanted_method]->getResult(); + } else { + throw new AlgorithmException($method); + } + + ($chrono !== null) && $chrono->setRole('GetResult for '.$method); + + return $result; + } + + + #[PublicAPI] + #[Description('Get the natural Condorcet winner if there is one. Alternatively you can get the winner(s) from an advanced Condorcet algorithm.')] + #[FunctionReturn("Candidate object given. Null if there are no available winner or loser.\n\nIf you use an advanced method instead of Natural, you can get an array with multiples winners.\n\nThrow an exception on error.")] + #[Example('Manual - Natural Condorcet', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-1.-Natural-Condorcet')] + #[Related('Election::getCondorcetWinner', 'Election::getLoser', 'Election::getResult')] + public function getWinner( + #[FunctionParameter("*Only if not null: *\n\nThe winner will be provided by an advanced algorithm of an available advanced Condorcet method. For most of them, it will be the same as the Condorcet Marquis there. But if it does not exist, it may be different; and in some cases they may be multiple. \n\nIf null, Natural Condorcet algorithm will be use.")] + ?string $method = null + ): array|Candidate|null { + $algo = Condorcet::condorcetBasicSubstitution($method); + + // ------- + + if ($algo === Condorcet::CONDORCET_BASIC_CLASS) { + (Condorcet::$UseTimer === true) && new Timer_Chrono($this->timer, 'GetWinner for CondorcetBasic'); + $this->initResult($algo); + $result = $this->Calculator[$algo]->getWinner(); + + return ($result === null) ? null : $this->getCandidateObjectFromKey($result); + } else { + return $this->getResult($algo)->getWinner(); + } + } + + + #[PublicAPI] + #[Description('Get the natural Condorcet loser if there is one. Alternatively you can get the loser(s) from an advanced Condorcet algorithm.')] + #[FunctionReturn("Candidate object given. Null if there are no available winner or loser.\n\nIf you use an advanced method instead of Natural, you can get an array with multiples losers.\n\nThrow an exception on error.")] + #[Example('Manual - Natural Condorcet', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-1.-Natural-Condorcet')] + #[Related('Election::getWinner', 'Election::getResult')] + public function getLoser( + #[FunctionParameter("*Only if not nulle:* \n\nThe loser will be provided by an advanced algorithm of an available advanced Condorcet method. For most of them, it will be the same as the Condorcet Marquis there. But if it does not exist, it may be different; and in some cases they may be multiple. \n\n If null, Natural Condorcet algorithm will be use.")] + ?string $method = null + ): array|Candidate|null { + $algo = Condorcet::condorcetBasicSubstitution($method); + + // ------- + + if ($algo === Condorcet::CONDORCET_BASIC_CLASS) { + (Condorcet::$UseTimer === true) && new Timer_Chrono($this->timer, 'GetLoser for CondorcetBasic'); + $this->initResult($algo); + $result = $this->Calculator[$algo]->getLoser(); + + return ($result === null) ? null : $this->getCandidateObjectFromKey($result); + } else { + return $this->getResult($algo)->getLoser(); + } + } + + #[PublicAPI] + #[Description('Get the natural Condorcet winner if there is one.')] + #[FunctionReturn('Candidate object given. Null if there are no available winner.')] + #[Example('Manual - Natural Condorcet', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-1.-Natural-Condorcet')] + #[Related('Election::getCondorcetLoser', 'Election::getWiner', 'Election::getResult')] + public function getCondorcetWinner(): ?Candidate + { + return $this->getWinner(null); + } + + #[PublicAPI] + #[Description('Get the natural Condorcet loser if there is one.')] + #[FunctionReturn('Candidate object given. Null if there are no available loser.')] + #[Example('Manual - Natural Condorcet', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-1.-Natural-Condorcet')] + #[Related('Election::getCondorcetWinner', 'Election::getLoser', 'Election::getResult')] + public function getCondorcetLoser(): ?Candidate + { + return $this->getLoser(null); + } + + #[PublicAPI] + #[Description('Return the Pairwise.')] + #[FunctionReturn('Pairwise object.')] + #[Example('Manual - Advanced Results', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-4.-Advanced-Results-Management')] + #[Related('Election::getExplicitPairwise', 'Election::getResult')] + public function getPairwise(): Pairwise + { + if ($this->Pairwise === null) { + $this->preparePairwiseAndCleanCompute(); + } + + return $this->Pairwise; + } + + #[PublicAPI] + #[Description('Return the Pairwise.')] + #[FunctionReturn('Pairwise as an explicit array .')] + #[Related('Election::getPairwise', 'Election::getResult')] + public function getExplicitPairwise(): array + { + return $this->getPairwise()->getExplicitPairwise(); + } + + // Generic function for default result with ability to change default object method + #[PublicAPI] + #[Description('Set an option to a method module and reset his cache for this election object. Be aware that this option applies to all election objects and remains in memory.')] + #[FunctionReturn('True on success. Else False.')] + #[Related('Result::getMethodOptions')] + public function setMethodOption( + #[FunctionParameter('Method name or class path')] + string $method, + #[FunctionParameter('Option name')] + string $optionName, + #[FunctionParameter('Option Value')] + array|\BackedEnum|int|float|string $optionValue + ): bool { + if ($method = Condorcet::getMethodClass($method)) { + $method::setOption($optionName, $optionValue); + unset($this->Calculator[$method]); + + return true; + } else { + return false; + } + } + + #[PublicAPI] + #[Description('The current level of stats verbosity for this election object. Look at Election->setStatsVerbosity method for more informations.')] + #[FunctionReturn('The current verbosity level for this election object.')] + public function getStatsVerbosity(): StatsVerbosity + { + return $this->StatsVerbosity; + } + + #[PublicAPI] + #[Description('Set a verbosity level for Result->statsVerbosity on returning Result objects. High level can slow down processing and use more memory (many more) than LOW and STD (default) level on somes methods.')] + #[Related('Election::getVerbosity', 'Result::getVerbosity')] + public function setStatsVerbosity( + #[FunctionParameter('A verbosity level')] + StatsVerbosity $StatsVerbosity + ): void { + if ($StatsVerbosity !== $this->StatsVerbosity) { + $this->cleanupCalculator(); + } + + $this->StatsVerbosity = $StatsVerbosity; + } + + + + /////////// MAKE RESULTS /////////// + + #[PublicAPI] + #[Description('Really similar to Election::getResult() but not return anything. Just calculates silently and fill the cache.')] + #[Related('Election::getWinner', 'Election::getResult', 'Condorcet::getDefaultMethod')] + public function computeResult( + #[FunctionParameter('Not requiered for use object default method. Set the string name of the algorithm for use an specific one')] + ?string $method = null + ): void { + $this->getResult($method); + } + + protected function makePairwise(): void + { + $this->cleanupCalculator(); + $this->Pairwise = new Pairwise($this); + } + + protected function initResult(string $class): void + { + if (!isset($this->Calculator[$class])) { + $this->Calculator[$class] = new $class($this); + } + } + + public function debugGetCalculator(): ?array + { + return $this->Calculator; + } + + // Cleanup results to compute again with new votes + protected function cleanupCompute(): void + { + // Algos + $this->cleanupCalculator(); + + // Clean pairwise + $this->Pairwise = null; + } + + public function cleanupCalculator(): void + { + $this->Calculator = null; + } + + + /////////// UTILS /////////// + + protected static function formatResultOptions(array $arg): array + { + // About tag filter + if (isset($arg['tags'])) { + $arg['%tagFilter'] = true; + + if (!isset($arg['withTag']) || !\is_bool($arg['withTag'])) { + $arg['withTag'] = true; + } + } else { + $arg['%tagFilter'] = false; + } + + return $arg; + } +} diff --git a/include/Condorcet/src/ElectionProcess/VotesProcess.php b/include/Condorcet/src/ElectionProcess/VotesProcess.php new file mode 100644 index 0000000..ba6307e --- /dev/null +++ b/include/Condorcet/src/ElectionProcess/VotesProcess.php @@ -0,0 +1,519 @@ +Votes->countVotes(VoteUtil::tagsConvert($tags), $with); + } + + #[PublicAPI] + #[Description('Count the number of actual invalid (if constraints functionality is enabled) but registered vote for this election.')] + #[FunctionReturn('Number of valid and registered vote into this election.')] + #[Related('Election::countValidVoteWithConstraints', 'Election::countVotes', 'Election::sumValidVotesWeightWithConstraints')] + public function countInvalidVoteWithConstraints(): int + { + return $this->Votes->countInvalidVoteWithConstraints(); + } + + #[PublicAPI] + #[Description("Count the number of actual registered and valid vote for this election. This method don't ignore votes constraints, only valid vote will be counted.")] + #[FunctionReturn('Number of valid and registered vote into this election.')] + #[Related('Election::countInvalidVoteWithConstraints', 'Election::countVotes', 'Election::sumValidVotesWeightWithConstraints')] + public function countValidVoteWithConstraints(): int + { + return $this->countVotes() - $this->countInvalidVoteWithConstraints(); + } + + // Sum votes weight + #[PublicAPI] + #[Description('Sum total votes weight in this election. If vote weight functionality is disable (default setting), it will return the number of registered votes. This method ignore votes constraints.')] + #[FunctionReturn('(Int) Total vote weight')] + #[Related('Election::sumValidVotesWeightWithConstraints')] + public function sumVotesWeight(): int + { + return $this->Votes->sumVotesWeight(false); + } + + #[PublicAPI] + #[Description("Sum total votes weight in this election. If vote weight functionality is disable (default setting), it will return the number of registered votes. This method don't ignore votes constraints, only valid vote will be counted.")] + #[FunctionReturn('(Int) Total vote weight')] + #[Related('Election::countValidVoteWithConstraints', 'Election::countInvalidVoteWithConstraints')] + public function sumValidVotesWeightWithConstraints(): int + { + return $this->Votes->sumVotesWeight(true); + } + + // Get the votes registered list + #[PublicAPI] + #[Description('Get registered vote list.')] + #[FunctionReturn('Populated by each Vote object.')] + #[Related('Election::countVotes', 'Election::getVotesListAsString')] + public function getVotesList( + #[FunctionParameter('Tags list as a string separated by commas or array')] + array|null|string $tags = null, + #[FunctionParameter('Get votes with these tags or without')] + bool $with = true + ): array { + return $this->Votes->getVotesList(VoteUtil::tagsConvert($tags), $with); + } + + #[PublicAPI] + #[Description('Get registered vote list.')] + #[FunctionReturn("Return a string like :
\nA > B > C * 3
\nA = B > C * 6")] + #[Related('Election::parseVotes')] + public function getVotesListAsString( + #[FunctionParameter('Depending of the implicit ranking rule of the election, will complete or not the ranking. If $withContext is false, ranking are never adapted to the context.')] + bool $withContext = true + ): string { + return $this->Votes->getVotesListAsString($withContext); + } + + public function getVotesManager(): VotesManager + { + return $this->Votes; + } + + #[PublicAPI] + #[Description("Same as Election::getVotesList. But Return a PHP generator object.\nUsefull if your work on very large election with an external DataHandler, because it's will not using large memory amount.")] + #[FunctionReturn('Populated by each Vote object.')] + #[Related('Election::getVotesList')] + public function getVotesListGenerator( + #[FunctionParameter('Tags list as a string separated by commas or array')] + array|null|string $tags = null, + #[FunctionParameter('Get votes with these tags or without')] + bool $with = true + ): \Generator { + return $this->Votes->getVotesListGenerator(VoteUtil::tagsConvert($tags), $with); + } + + #[PublicAPI] + #[Description("Same as Election::getVotesList. But Return a PHP generator object.\nUsefull if your work on very large election with an external DataHandler, because it's will not using large memory amount.")] + #[FunctionReturn('Populated by each Vote object.')] + #[Related('Election::getVotesListGenerator', 'Election::getVotesList')] + public function getVotesValidUnderConstraintGenerator( + #[FunctionParameter('Tags list as a string separated by commas or array')] + array|null|string $tags = null, + #[FunctionParameter('Get votes with these tags or without')] + bool $with = true + ): \Generator { + return $this->Votes->getVotesValidUnderConstraintGenerator($tags, $with); + } + + #[InternalModulesAPI] + public function getVoteKey(Vote $vote): ?int + { + return $this->Votes->getVoteKey($vote); + } + + + /////////// ADD & REMOVE VOTE /////////// + + // Add a single vote. Array key is the rank, each candidate in a rank are separate by ',' It is not necessary to register the last rank. + #[PublicAPI] + #[Description('Add a vote to an election.')] + #[FunctionReturn('The vote object.')] + #[Throws(VoteMaxNumberReachedException::class)] + #[Example('Manual - Vote Management', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote')] + #[Related('Election::parseVotes', 'Election::addVotesFromJson', 'Election::removeVote', 'Election::getVotesList')] + public function addVote( + #[FunctionParameter('String or array representation. Or CondorcetPHP\Condorcet\Vote object. If you not provide yourself Vote object, a new one will be generate for you')] + array|string|Vote $vote, + #[FunctionParameter('String separated by commas or an array. Will add tags to the vote object for you. But you can too add it yourself to Vote object')] + array|string|null $tags = null + ): Vote { + $this->prepareVoteInput($vote, $tags); + + // Check Max Vote Count + if (self::$maxVoteNumber !== null && $this->countVotes() >= self::$maxVoteNumber) { + throw new VoteMaxNumberReachedException(self::$maxVoteNumber); + } + + // Register vote + return $this->registerVote($vote, $tags); // Return the vote object + } + + public function prepareUpdateVote(Vote $existVote): void + { + $this->Votes->UpdateAndResetComputing(key: $this->getVoteKey($existVote), type: 2); + } + + public function finishUpdateVote(Vote $existVote): void + { + $this->Votes->UpdateAndResetComputing(key: $this->getVoteKey($existVote), type: 1); + + if ($this->Votes->isUsingHandler()) { + $this->Votes[$this->getVoteKey($existVote)] = $existVote; + } + } + + public function checkVoteCandidate(Vote $vote): bool + { + if ($this->voteFastMode === 0) { + $linkCount = $vote->countLinks(); + $linkCheck = ($linkCount === 0 || ($linkCount === 1 && $vote->haveLink($this))); + + foreach ($vote->getAllCandidates() as $candidate) { + if (!$linkCheck && $candidate->getProvisionalState() && !$this->isRegisteredCandidate(candidate: $candidate, strictMode: true) && $this->isRegisteredCandidate(candidate: $candidate, strictMode: false)) { + return false; + } + } + } + + if ($this->voteFastMode < 2) { + $ranking = $vote->getRanking(); + + $change = $this->convertRankingCandidates($ranking); + + if ($change) { + $vote->setRanking( + $ranking, + (abs($vote->getTimestamp() - microtime(true)) > 0.5) ? ($vote->getTimestamp() + 0.001) : null + ); + } + } + + return true; + } + + public function convertRankingCandidates(array &$ranking): bool + { + $change = false; + + foreach ($ranking as &$choice) { + foreach ($choice as &$candidate) { + if (!$this->isRegisteredCandidate($candidate, true)) { + if ($candidate->getProvisionalState() && $this->isRegisteredCandidate(candidate: $candidate, strictMode: false)) { + $candidate = $this->Candidates[$this->getCandidateKey((string) $candidate)]; + $change = true; + } + } + } + } + + return $change; + } + + // Write a new vote + protected function registerVote(Vote $vote, array|string|null $tags): Vote + { + // Vote identifiant + $tags === null || $vote->addTags($tags); + + // Register + try { + $vote->registerLink($this); + $this->Votes[] = $vote; + } catch (CondorcetInternalException $e) { + // Security : Check if vote object is not already registered + throw new VoteException('seats are already registered'); + } + + return $vote; + } + + #[PublicAPI] + #[Description('Remove Votes from an election.')] + #[FunctionReturn("List of removed CondorcetPHP\Condorcet\Vote object.")] + #[Example('Manual - Vote management', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-2.-Manage-Vote')] + #[Related('Election::addVote', 'Election::getVotesList', 'Election::removeVotesByTags')] + public function removeVote( + #[FunctionParameter('Vote object')] + Vote $vote + ): bool { + $key = $this->getVoteKey($vote); + if ($key !== null) { + $deletedVote = $this->Votes[$key]; + + unset($this->Votes[$key]); + + $deletedVote->destroyLink($this); + + return true; + } else { + throw new VoteException('cannot remove vote, is not registered in this election'); + } + } + + #[PublicAPI] + #[Description("Remove Vote from an election using tags.\n\n```php\n\$election->removeVotesByTags('Charlie') ; // Remove vote(s) with tag Charlie\n\$election->removeVotesByTags('Charlie', false) ; // Remove votes without tag Charlie\n\$election->removeVotesByTags('Charlie, Julien', false) ; // Remove votes without tag Charlie AND without tag Julien.\n\$election->removeVotesByTags(array('Julien','Charlie')) ; // Remove votes with tag Charlie OR with tag Julien.\n```")] + #[FunctionReturn("List of removed CondorcetPHP\Condorcet\Vote object.")] + #[Example('Manual - Vote management', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-2.-Manage-Vote')] + #[Related('Election::addVote', 'Election::getVotesList', 'Election::removeVotes')] + public function removeVotesByTags( + #[FunctionParameter('Tags as string separated by commas or array')] + array|string $tags, + #[FunctionParameter('Votes with these tags or without')] + bool $with = true + ): array { + $rem = []; + + // Prepare Tags + $tags = VoteUtil::tagsConvert($tags); + + // Deleting + foreach ($this->getVotesList($tags, $with) as $key => $value) { + $deletedVote = $this->Votes[$key]; + $rem[] = $deletedVote; + + unset($this->Votes[$key]); + + $deletedVote->destroyLink($this); + } + + return $rem; + } + + + /////////// PARSE VOTE /////////// + + // Return the well formatted vote to use. + #[Throws(VoteInvalidFormatException::class)] + protected function prepareVoteInput(array|string|Vote &$vote, array|string|null $tags = null): void + { + if (!($vote instanceof Vote)) { + $vote = new Vote(ranking: $vote, tags: $tags, ownTimestamp: null, electionContext: $this); + } + + // Check array format && Make checkVoteCandidate + if (!$this->checkVoteCandidate($vote)) { + throw new VoteInvalidFormatException; + } + } + + #[PublicAPI] + #[Description('Import votes from a Json source.')] + #[FunctionReturn('Count of new registered vote.')] + #[Example('Manual - Add Vote', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote')] + #[Related('Election::addVote', 'Election::parseVotes', 'Election::addCandidatesFromJson')] + public function addVotesFromJson( + #[FunctionParameter('Json string input')] + string $input + ): int { + $input = CondorcetUtil::prepareJson($input); + + $adding = []; + $count = 0; + + foreach ($input as $record) { + if (empty($record['vote'])) { + continue; + } + + $vote = $record['vote']; + $tags = !isset($record['tag']) ? null : $record['tag']; + $multiple = !isset($record['multi']) ? 1 : (int) $record['multi']; + $weight = !isset($record['weight']) ? 1 : (int) $record['weight']; + + $this->synthesisVoteFromParse($count, $multiple, $adding, $vote, $tags, $weight); + } + + $this->doAddVotesFromParse($adding); + + return $count; + } + + #[PublicAPI] + #[Description('Import votes from a text source. If any invalid vote is found inside, nothing are registered.')] + #[FunctionReturn('Count of the new registered vote.')] + #[Example('Manual - Add Vote', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote')] + #[Related('Election::addVote', 'Election::parseCandidates', 'Election::parseVotesWithoutFail', 'Election::addVotesFromJson')] + public function parseVotes( + #[FunctionParameter('String or valid path to a text file')] + string $input, + #[FunctionParameter('If true, the input is evalatued as path to text file')] + bool $isFile = false + ): int { + $input = CondorcetUtil::prepareParse($input, $isFile); + + $adding = []; + $count = 0; + + foreach ($input as $line) { + $voteParser = new VoteEntryParser($line); + + $this->synthesisVoteFromParse( + count: $count, + multiple: $voteParser->multiple, + adding: $adding, + vote: $voteParser->ranking, + tags: $voteParser->tags, + weight: $voteParser->weight, + ); + } + + $this->doAddVotesFromParse($adding); + + return $count; + } + + #[PublicAPI] + #[Description('Similar to parseVote method. But will ignore invalid line. This method is also far less greedy in memory and must be prefered for very large file input. And to combine with the use of an external data handler.')] + #[FunctionReturn("Number of invalid records into input (except empty lines). It's not an invalid votes count. Check Election::countVotes if you want to be sure.")] + #[Example('Manual - Add Vote', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote')] + #[Related('Election::addVote', 'Election::parseCandidates', 'Election::parseVotes', 'Election::addVotesFromJson')] + public function parseVotesWithoutFail( + #[FunctionParameter('String, valid path to a text file or an object SplFileInfo or extending it like SplFileObject')] + \SplFileInfo|string $input, + #[FunctionParameter('If true, the string input is evalatued as path to text file')] + bool $isFile = false, + #[FunctionParameter('Callback function to execute after each registered vote')] + ?\Closure $callBack = null + ): int { + $inserted_votes_count = 0; + $fail_count = 0; + $doCallBack = $callBack !== null; + + if (!$isFile && !($input instanceof \SplFileInfo)) { + $file = new \SplTempFileObject(256 * 1024 * 1024); + $file->fwrite($input); + } elseif ($input instanceof \SplFileObject) { + $file = $input; + } else { + $file = ($input instanceof \SplFileInfo) ? $input : new \SplFileInfo($input); + + if ($file->isFile() && $file->isReadable()) { + $file = ($file instanceof \SplFileObject) ? $file : $file->openFile('r'); + } else { + throw new FileDoesNotExistException('Specified input file does not exist. path: '.$input); + } + } + + unset($input); // Memory Optimization + $file->setFlags($file->getFlags() | \SplFileObject::SKIP_EMPTY); + $file->rewind(); + + $char = ''; + $record = ''; + + while ($char !== false) { + $char = $file->fgetc(); + + if ($char === ';' || $char === "\n" || $char === '#' || $char === false) { + if ($char === '#') { + $record .= $char; + + while (($char = $file->fgetc()) !== false) { + if ($char === "\n") { + break; + } else { + $record .= $char; + } + } + } + + try { + $parsedVote = new VoteEntryParser($record); + + if ($parsedVote->ranking === null) { + $record = ''; + continue; + } + + $count = 0; + $adding = []; + + $this->synthesisVoteFromParse( + count: $count, + adding: $adding, + vote: $parsedVote->ranking, + tags: $parsedVote->tags, + weight: $parsedVote->weight, + multiple: $parsedVote->multiple + ); + + $this->doAddVotesFromParse($adding); + + $inserted_votes_count += $count; + + if ($doCallBack) { + $doCallBack = $callBack($inserted_votes_count); + } + } catch (VoteInvalidFormatException) { + ++$fail_count; + } finally { + $record = ''; + } + } else { + $record .= $char; + } + } + + return $fail_count; + } + + protected function synthesisVoteFromParse(int &$count, int $multiple, array &$adding, array|string|Vote $vote, null|array|string $tags, int $weight): void + { + $adding_predicted_count = $count + $multiple; + + if (self::$maxVoteNumber && self::$maxVoteNumber < ($this->countVotes() + $adding_predicted_count)) { + throw new VoteMaxNumberReachedException(self::$maxParseIteration); + } + + if (self::$maxParseIteration !== null && $adding_predicted_count >= self::$maxParseIteration) { + throw new VoteMaxNumberReachedException(self::$maxParseIteration); + } + + $newVote = new Vote(ranking: $vote, tags: $tags, electionContext: $this); + $newVote->setWeight($weight); + + $adding[] = ['multiple' => $multiple, 'vote' => $newVote]; + + $count += $multiple; + } + + protected function doAddVotesFromParse(array $adding): void + { + $this->voteFastMode = 1; + + foreach ($adding as $oneLine) { + for ($i = 1; $i <= $oneLine['multiple']; $i++) { + if ($i === 1) { + $finalVoteModel = $this->addVote($oneLine['vote']); + $this->voteFastMode = 2; + } else { + $this->addVote(clone $finalVoteModel); + } + } + } + + $this->voteFastMode = 0; + } +} diff --git a/include/Condorcet/src/Linkable.php b/include/Condorcet/src/Linkable.php new file mode 100644 index 0000000..bb982b9 --- /dev/null +++ b/include/Condorcet/src/Linkable.php @@ -0,0 +1,93 @@ +destroyAllLink(); + } + + #[PublicAPI] + #[Description('Check if this election is linked with this Candidate/Vote object.')] + #[FunctionReturn('True or False.')] + #[Related('Vote::countLinks', 'Candidate::countLinks', 'Vote::getLinks', 'Candidate::getLinks', 'Vote::haveLink', 'Candidate::haveLink')] + public function haveLink( + #[FunctionParameter('Condorcet election to check')] + Election $election + ): bool { + $this->initWeakMap(); + + return $this->link->offsetExists($election); + } + + #[PublicAPI] + #[Description('Count number of linked election to this object.')] + #[FunctionReturn('Number of linked elections.')] + #[Related('Vote::countLinks', 'Candidate::countLinks', 'Vote::getLinks', 'Candidate::getLinks', 'Vote::haveLink', 'Candidate::haveLink')] + public function countLinks(): int + { + $this->initWeakMap(); + + return \count($this->link); + } + + #[PublicAPI] + #[Description('Get elections object linked to this Vote or Candidate object.')] + #[FunctionReturn('Populated by each elections Condorcet object.')] + #[Related('Vote::countLinks', 'Candidate::countLinks', 'Vote::getLinks', 'Candidate::getLinks', 'Vote::haveLink', 'Candidate::haveLink')] + public function getLinks(): \WeakMap + { + $this->initWeakMap(); + + return $this->link; + } + + // Internal + # Dot not Overloading ! Do not Use ! + + protected function initWeakMap(): void + { + $this->link ??= new \WeakMap; + } + + public function registerLink(Election $election): void + { + if (!$this->haveLink($election)) { // haveLink will initWeakmap if necessary + $this->link->offsetSet($election, true); + } else { + throw new CondorcetInternalException('Link is already registered.'); + } + } + + public function destroyLink(Election $election): bool + { + if ($this->haveLink($election)) { // haveLink will initWeakmap if necessary + $this->link->offsetUnset($election); + return true; + } else { + return false; + } + } + + protected function destroyAllLink(): void + { + $this->link = null; + $this->initWeakMap(); + } +} diff --git a/include/Condorcet/src/Result.php b/include/Condorcet/src/Result.php new file mode 100644 index 0000000..08d1f64 --- /dev/null +++ b/include/Condorcet/src/Result.php @@ -0,0 +1,354 @@ +ResultIterator); + } + + public function current(): array|Candidate + { + return current($this->ResultIterator); + } + + public function key(): int + { + return key($this->ResultIterator); + } + + public function next(): void + { + next($this->ResultIterator); + } + + public function valid(): bool + { + return key($this->ResultIterator) !== null; + } + + // Implement ArrayAccess + + #[Throws(ResultException::class)] + public function offsetSet(mixed $offset, mixed $value): void + { + throw new ResultException; + } + + public function offsetExists(mixed $offset): bool + { + return isset($this->ranking[$offset]); + } + + #[Throws(ResultException::class)] + public function offsetUnset(mixed $offset): void + { + throw new ResultException; + } + + public function offsetGet(mixed $offset): array|Candidate|null + { + return $this->ranking[$offset] ?? null; + } + + // Implement Countable + + public function count(): int + { + return \count($this->ranking); + } + + + /////////// CONSTRUCTOR /////////// + + protected readonly array $Result; + protected array $ResultIterator; + protected $Stats; + protected array $warning = []; + + #[PublicAPI] + public readonly array $ranking; + #[PublicAPI] + public readonly array $rankingAsString; + #[PublicAPI] + public readonly ?int $seats; + #[PublicAPI] + public readonly array $methodOptions; + #[PublicAPI] + public readonly ?Candidate $CondorcetWinner; + #[PublicAPI] + public readonly ?Candidate $CondorcetLoser; + #[PublicAPI] + public readonly float $buildTimestamp; + #[PublicAPI] + public readonly string $fromMethod; + #[PublicAPI] + public readonly string $byClass; + #[PublicAPI] + public readonly StatsVerbosity $statsVerbosity; + #[PublicAPI] + public readonly string $electionCondorcetVersion; + + + #[InternalModulesAPI] + public function __construct(string $fromMethod, string $byClass, Election $election, array $result, $stats, ?int $seats = null, array $methodOptions = []) + { + ksort($result, \SORT_NUMERIC); + + $this->Result = $result; + $this->ResultIterator = $this->ranking = $this->makeUserResult($election); + $this->rankingAsString = $this->getResultAsArray(true); + $this->Stats = $stats; + $this->seats = $seats; + $this->fromMethod = $fromMethod; + $this->byClass = $byClass; + $this->statsVerbosity = $election->getStatsVerbosity(); + $this->electionCondorcetVersion = $election->getObjectVersion(); + $this->CondorcetWinner = $election->getWinner(); + $this->CondorcetLoser = $election->getLoser(); + $this->buildTimestamp = microtime(true); + $this->methodOptions = $methodOptions; + } + + + /////////// Get Result /////////// + + #[PublicAPI] + #[Description('Get result as an array')] + #[FunctionReturn('An ordered multidimensionnal array by rank.')] + #[Related('Election::getResult', 'Result::getResultAsString')] + public function getResultAsArray( + #[FunctionParameter('Convert Candidate object to string')] + bool $convertToString = false + ): array { + $r = $this->ranking; + + foreach ($r as &$rank) { + if (\count($rank) === 1) { + $rank = $convertToString ? (string) $rank[0] : $rank[0]; + } elseif ($convertToString) { + foreach ($rank as &$subRank) { + $subRank = (string) $subRank; + } + } + } + + return $r; + } + + #[PublicAPI] + #[Description('Get result as string')] + #[FunctionReturn('Result ranking as string.')] + #[Related('Election::getResult', 'Result::getResultAsArray')] + public function getResultAsString(): string + { + return VoteUtil::getRankingAsString($this->getResultAsArray(true)); + } + + #[PublicAPI] + #[Description('Get result as an array')] + #[FunctionReturn("Unlike other methods to recover the result. This is frozen as soon as the original creation of the Result object is created.\nCandidate objects are therefore protected from any change of candidateName, since the candidate objects are converted into a string when the results are promulgated.\n\nThis control method can therefore be useful if you undertake suspicious operations on candidate objects after the results have been promulgated.")] + #[Related('Result::getResultAsArray', 'Result::getResultAsString')] + public function getOriginalResultArrayWithString(): array + { + return $this->rankingAsString; + } + + #[InternalModulesAPI] + public function getResultAsInternalKey(): array + { + return $this->Result; + } + + #[PublicAPI] + #[Description('Get advanced computing data from used algorithm. Like Strongest paths for Schulze method.')] + #[FunctionReturn('Varying according to the algorithm used.')] + #[Example('Advanced Result Management', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-C.-Result-%23-3.-Advanced-Results-Management')] + #[Related('Election::getResult')] + public function getStats(): mixed + { + return $this->Stats; + } + + #[PublicAPI] + #[Description('Equivalent to [Condorcet/Election::getWinner($method)](../Election Class/public Election--getWinner.md).')] + #[FunctionReturn("Candidate object given. Null if there are no available winner.\nYou can get an array with multiples winners.")] + #[Related('Result::getLoser', 'Election::getWinner')] + public function getWinner(): array|Candidate|null + { + return CondorcetUtil::format($this[1], false); + } + + #[PublicAPI] + #[Description('Equivalent to [Condorcet/Election::getWinner($method)](../Election Class/public Election--getWinner.md).')] + #[FunctionReturn("Candidate object given. Null if there are no available loser.\nYou can get an array with multiples losers.")] + #[Related('Result::getWinner', 'Election::getLoser')] + public function getLoser(): array|Candidate|null + { + return CondorcetUtil::format($this[\count($this)], false); + } + + #[PublicAPI] + #[Description('Get the Condorcet winner, if exist, at the result time.')] + #[FunctionReturn("CondorcetPHP\Condorcet\Candidate object if there is a Condorcet winner or NULL instead.")] + #[Related('Result::getCondorcetLoser', 'Election::getWinner')] + public function getCondorcetWinner(): ?Candidate + { + return $this->CondorcetWinner; + } + + #[PublicAPI] + #[Description('Get the Condorcet loser, if exist, at the result time.')] + #[FunctionReturn('Condorcet/Candidate object if there is a Condorcet loser or NULL instead.')] + #[Related('Result::getCondorcetWinner', 'Election::getLoser')] + public function getCondorcetLoser(): ?Candidate + { + return $this->CondorcetLoser; + } + + protected function makeUserResult(Election $election): array + { + $userResult = []; + + foreach ($this->Result as $key => $value) { + if (\is_array($value)) { + foreach ($value as $candidate_key) { + $userResult[$key][] = $election->getCandidateObjectFromKey($candidate_key); + } + + sort($userResult[$key], \SORT_STRING); + } elseif ($value === null) { + $userResult[$key] = null; + } else { + $userResult[$key][] = $election->getCandidateObjectFromKey($value); + } + } + + foreach ($userResult as $key => $value) { + if ($value === null) { + $userResult[$key] = null; + } + } + + return $userResult; + } + + + /////////// Get & Set MetaData /////////// + + #[InternalModulesAPI] + public function addWarning(int $type, ?string $msg = null): bool + { + $this->warning[] = ['type' => $type, 'msg' => $msg]; + + return true; + } + + #[PublicAPI] + #[Description('From native methods: only Kemeny-Young use it to inform about a conflict during the computation process.')] + #[FunctionReturn('Warnings provided by the by the method that generated the warning. Empty array if there is not.')] + public function getWarning( + #[FunctionParameter('Filter on a specific warning type code')] + ?int $type = null + ): array { + if ($type === null) { + return $this->warning; + } else { + $r = []; + + foreach ($this->warning as $oneWarning) { + if ($oneWarning['type'] === $type) { + $r[] = $oneWarning; + } + } + + return $r; + } + } + + #[PublicAPI] + #[Description('Get the The algorithmic method used for this result.')] + #[FunctionReturn("Method class path like CondorcetPHP\Condorcet\Algo\Methods\Copeland")] + #[Related('Result::getMethod')] + public function getClassGenerator(): string + { + return $this->byClass; + } + + #[PublicAPI] + #[Description('Get the The algorithmic method used for this result.')] + #[FunctionReturn('Method name.')] + #[Related('Result::getClassGenerator')] + public function getMethod(): string + { + return $this->fromMethod; + } + + #[PublicAPI] + #[Description('Return the method options.')] + #[FunctionReturn('Array of options. Can be empty for most of the methods.')] + #[Related('Result::getClassGenerator')] + public function getMethodOptions(): array + { + $r = $this->methodOptions; + + if ($this->isProportional()) { + $r['Seats'] = $this->getNumberOfSeats(); + } + + return $r; + } + + #[PublicAPI] + #[Description('Get the timestamp of this result.')] + #[FunctionReturn('Microsecond timestamp.')] + public function getBuildTimeStamp(): float + { + return (float) $this->buildTimestamp; + } + + #[PublicAPI] + #[Description('Get the Condorcet PHP version that build this Result.')] + #[FunctionReturn('Condorcet PHP version string format.')] + public function getCondorcetElectionGeneratorVersion(): string + { + return $this->electionCondorcetVersion; + } + + #[PublicAPI] + #[Description('Get number of Seats for STV methods result.')] + #[FunctionReturn('Number of seats if this result is a STV method. Else NULL.')] + #[Related('Election::setNumberOfSeats', 'Election::getNumberOfSeats')] + public function getNumberOfSeats(): ?int + { + return $this->seats; + } + + #[PublicAPI] + #[Description('Does the result come from a proportional method')] + #[Related('Result::getNumberOfSeats')] + public function isProportional(): bool + { + return $this->seats !== null; + } +} diff --git a/include/Condorcet/src/Throwable/AlgorithmException.php b/include/Condorcet/src/Throwable/AlgorithmException.php new file mode 100644 index 0000000..78ea096 --- /dev/null +++ b/include/Condorcet/src/Throwable/AlgorithmException.php @@ -0,0 +1,17 @@ +message}: The method '{$method}' is configured to accept only {$maxCandidates} candidates"); + } +} diff --git a/include/Condorcet/src/Throwable/CondorcetPublicApiException.php b/include/Condorcet/src/Throwable/CondorcetPublicApiException.php new file mode 100644 index 0000000..befe13e --- /dev/null +++ b/include/Condorcet/src/Throwable/CondorcetPublicApiException.php @@ -0,0 +1,30 @@ +message .= ': ' . $message; + } + + parent::__construct($this->message); + } +} diff --git a/include/Condorcet/src/Throwable/DataHandlerException.php b/include/Condorcet/src/Throwable/DataHandlerException.php new file mode 100644 index 0000000..25ecaef --- /dev/null +++ b/include/Condorcet/src/Throwable/DataHandlerException.php @@ -0,0 +1,17 @@ +method = $method; + + if ($message !== null) { + $this->message = $message; + } + } +} diff --git a/include/Condorcet/src/Throwable/NoCandidatesException.php b/include/Condorcet/src/Throwable/NoCandidatesException.php new file mode 100644 index 0000000..599b350 --- /dev/null +++ b/include/Condorcet/src/Throwable/NoCandidatesException.php @@ -0,0 +1,17 @@ +manager = $timer; + $this->setRole($role); + $this->resetStart(); + $this->managerStartDeclare(); + } + + public function __destruct() + { + $this->manager->addTime($this); + } + + public function getStart(): float + { + return $this->start; + } + + public function getTimerManager(): Manager + { + return $this->manager; + } + + protected function resetStart(): void + { + $this->start = microtime(true); + } + + public function getRole(): ?string + { + return $this->role; + } + + public function setRole(?string $role): void + { + $this->role = $role; + } + + protected function managerStartDeclare(): void + { + $this->manager->startDeclare($this); + } +} diff --git a/include/Condorcet/src/Timer/Manager.php b/include/Condorcet/src/Timer/Manager.php new file mode 100644 index 0000000..4e30370 --- /dev/null +++ b/include/Condorcet/src/Timer/Manager.php @@ -0,0 +1,85 @@ +getTimerManager() === $this) { + if ($this->lastChronoTimestamp === null && $chrono->getStart() !== $this->startDeclare) { + return; + } + + $m = microtime(true); + + if ($this->lastChronoTimestamp > $chrono->getStart()) { + $c = $this->lastChronoTimestamp; + } else { + $c = $chrono->getStart(); + $this->history[] = ['role' => $chrono->getRole(), + 'process_in' => ($m - $c), + 'timer_start' => $c, + 'timer_end' => $m, + ]; + } + + $this->globalTimer += ($m - $c); + + $this->lastTimer = ($m - $chrono->getStart()); + $this->lastChronoTimestamp = $m; + } else { + throw new TimerException; + } + } + + public function getGlobalTimer(): float + { + return $this->globalTimer; + } + + public function getLastTimer(): float + { + return $this->lastTimer; + } + + #[PublicAPI] + #[Description('Return benchmarked actions history.')] + #[FunctionReturn('An explicit array with history.')] + #[Related('Election::getTimerManager')] + public function getHistory(): array + { + return $this->history; + } + + public function startDeclare(Chrono $chrono): static + { + if ($this->startDeclare === null) { + $this->startDeclare = $chrono->getStart(); + } + + return $this; + } +} diff --git a/include/Condorcet/src/Tools/Converters/CondorcetElectionFormat.php b/include/Condorcet/src/Tools/Converters/CondorcetElectionFormat.php new file mode 100644 index 0000000..2fbcf10 --- /dev/null +++ b/include/Condorcet/src/Tools/Converters/CondorcetElectionFormat.php @@ -0,0 +1,262 @@ +getCandidatesListAsString()) . "\n"; + $r .= ($includeNumberOfSeats) ? '#/Number of Seats: ' . $election->getNumberOfSeats() . "\n" : null; + $r .= '#/Implicit Ranking: ' . ($election->getImplicitRankingRule() ? 'true' : 'false') . "\n"; + $r .= '#/Weight Allowed: ' . ($election->isVoteWeightAllowed() ? 'true' : 'false') . "\n"; + // $r .= "\n"; + + if ($file) { + $file->fwrite($r); + $r = ''; + } + + if ($aggregateVotes) { + $r .= "\n" . $election->getVotesListAsString($inContext); + if ($file) { + $file->fwrite($r); + } + } else { + foreach ($election->getVotesListGenerator() as $vote) { + $line = "\n"; + $line .= ($includeTags && !empty($vote->getTags())) ? $vote->getTagsAsString().' || ' : ''; + + $voteString = $vote->getSimpleRanking($inContext ? $election : null); + $line .= !empty($voteString) ? $voteString : self::SPECIAL_KEYWORD_EMPTY_RANKING; + + if ($file) { + $file->fwrite($line); + } else { + $r .= $line; + } + } + } + + return ($file) ? null : $r; + } + + + ////// # Reader Object ////// + + # Reader Object + + // Const + protected const CANDIDATES_PATTERN = '/^#\/Candidates:(?.+)$/mi'; + protected const SEATS_PATTERN = '/^#\/Number of Seats: *(?[0-9]+) *$/mi'; + protected const IMPLICIT_PATTERN = '/^#\/Implicit Ranking: *(?(true|false)) *$/mi'; + protected const WEIGHT_PATTERN = '/^#\/Weight Allowed: *(?(true|false)) *$/mi'; + + + // Properties + protected \SplFileObject $file; + + #[PublicAPI] + public readonly array $candidates; + #[PublicAPI] + public readonly int $numberOfSeats; + #[PublicAPI] + public readonly bool $implicitRanking; + #[PublicAPI] + public readonly bool $voteWeight; + + #[PublicAPI] + public readonly bool $CandidatesParsedFromVotes; + #[PublicAPI] + public readonly int $invalidBlocksCount; + + // Read + + #[PublicAPI] + #[Description('Read a Condorcet format file, usually using .cvotes file extension')] + public function __construct( + #[FunctionParameter('String, valid path to a text file or an object SplFileInfo or extending it like SplFileObject')] + \SplFileInfo|string $input + ) { + $input = ($input instanceof \SplFileInfo) ? $input : new \SplFileInfo($input); + + if ($input instanceof \SplFileObject) { + $this->file = $input; + } elseif ($input->isFile() && $input->isReadable()) { + $this->file = $input->openFile('r'); + } else { + throw new FileDoesNotExistException('Specified input file does not exist. path: '.$input); + } + + unset($input); // Memory Optimization + + $this->file->setFlags(\SplFileObject::SKIP_EMPTY | \SplFileObject::DROP_NEW_LINE); + + $this->readParameters(); + + // Parse candidate directly from votes + if (empty($this->candidates)) { + $this->parseCandidatesFromVotes(); + $this->CandidatesParsedFromVotes = true; + } else { + $this->CandidatesParsedFromVotes = false; + } + } + + #[PublicAPI] + #[Description('Add the data to an election object')] + #[FunctionReturn('The election object')] + #[Related("Tools\DavidHillFormat::setDataToAnElection", "Tools\DebianFormat::setDataToAnElection")] + public function setDataToAnElection( + #[FunctionParameter('Add an existing election, useful if you want to set up some parameters or add extra candidates. If null an election object will be created for you.')] + Election $election = new Election, + #[FunctionParameter('Callback function to execute after each registered vote.')] + ?\Closure $callBack = null + ): Election { + // Parameters + // Set number of seats if specified in file + ($this->numberOfSeats ?? false) && $election->setNumberOfSeats($this->numberOfSeats); + + // Set explicit pairwise mode if specified in file + $this->implicitRanking ??= $election->getImplicitRankingRule(); + $election->setImplicitRanking($this->implicitRanking); + + // Set Vote weight (Condorcet disable it by default) + $this->voteWeight ??= $election->isVoteWeightAllowed(); + $election->allowsVoteWeight($this->voteWeight); + + + // Candidates + foreach ($this->candidates as $oneCandidate) { + $election->addCandidate($oneCandidate); + } + + // Votes + $this->file->rewind(); + $this->invalidBlocksCount = $election->parseVotesWithoutFail(input: $this->file, callBack: $callBack); + + return $election; + } + + // Internal + + protected function addCandidates(array $candidates): void + { + sort($candidates, \SORT_NATURAL); + $this->candidates = $candidates; + } + + protected function readParameters(): void + { + $this->file->rewind(); + + while (!$this->file->eof()) { + $line = $this->file->fgets(); + $matches = []; + + if (!isset($this->candidates) && preg_match(self::CANDIDATES_PATTERN, $line, $matches)) { + $parse = $matches['candidates']; + $parse = CondorcetUtil::prepareParse($parse, false); + + foreach ($parse as &$oneCandidate) { + $oneCandidate = new Candidate($oneCandidate); + } + + $this->addCandidates($parse); + } elseif (!isset($this->numberOfSeats) && preg_match(self::SEATS_PATTERN, $line, $matches)) { + $this->numberOfSeats = (int) $matches['seats']; + } elseif (!isset($this->implicitRanking) && preg_match(self::IMPLICIT_PATTERN, $line, $matches)) { + $parse = mb_strtolower($matches['implicitRanking']); + $this->implicitRanking = $this->boolParser($parse); + } elseif (!isset($this->voteWeight) && preg_match(self::WEIGHT_PATTERN, $line, $matches)) { + $parse = mb_strtolower($matches['weight']); + $this->voteWeight = $this->boolParser($parse); + } elseif (!empty($line) && !str_starts_with($line, '#')) { + break; + } + } + } + + protected function parseCandidatesFromVotes(): void + { + $this->file->rewind(); + + $candidates = []; + + while (!$this->file->eof()) { + $line = $this->file->fgets(); + + if (!empty($line) && !str_starts_with($line, '#')) { + if (($pos = mb_strpos($line, '||')) !== false) { + $line = mb_substr($line, ($pos + 2)); + } + + if (($pos = mb_strpos($line, '||')) !== false) { + $line = mb_substr($line, ($pos + 2)); + } + + foreach (['#', '*', '^'] as $c) { + if (($pos = mb_strpos($line, $c)) !== false) { + $line = mb_substr($line, 0, $pos); + } + } + + $line = str_replace('>', '=', $line); + $line = explode('=', $line); + + foreach ($line as $oneCandidate) { + $oneCandidate = trim($oneCandidate); + + if ($oneCandidate !== self::SPECIAL_KEYWORD_EMPTY_RANKING) { + $candidates[$oneCandidate] = null; + } + } + } + } + + $this->addCandidates(array_keys($candidates)); + } + + protected function boolParser(string $parse): bool + { + return match ($parse) { + 'true' => true, + 'false' => false, + default => true + }; + } +} diff --git a/include/Condorcet/src/Tools/Converters/ConverterInterface.php b/include/Condorcet/src/Tools/Converters/ConverterInterface.php new file mode 100644 index 0000000..59a9b72 --- /dev/null +++ b/include/Condorcet/src/Tools/Converters/ConverterInterface.php @@ -0,0 +1,26 @@ +lines = file($filePath, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES); + (end($this->lines) === '') ? array_pop($this->lines) : null; # Remove bad format from most popular source for this format (elections A01 and A04) + + $this->readNumberOfSeats(); + $this->readCandidatesNames(); + $this->readVotes(); + } + + #[PublicAPI] + #[Description('Add the data to an election object')] + #[FunctionReturn('The election object')] + #[Related("Tools\CondorcetElectionFormat::setDataToAnElection", "Tools\DebianFormat::setDataToAnElection")] + public function setDataToAnElection( + #[FunctionParameter('Add an existing election, useful if you want to set up some parameters or add extra candidates. If null an election object will be created for you.')] + ?Election $election = null + ): Election { + if ($election === null) { + $election = new Election; + } + + $election->setNumberOfSeats($this->NumberOfSeats); + + foreach ($this->candidates as $oneCandidate) { + $election->addCandidate($oneCandidate); + } + + foreach ($this->lines as $oneVote) { + $election->addVote($oneVote); + } + + return $election; + } + + // Internal + protected function readNumberOfSeats(): void + { + $first_line = reset($this->lines); + + $this->NumberOfSeats = (int) explode(' ', $first_line)[1]; + } + + protected function readCandidatesNames(): void + { + $last_line = end($this->lines); + $last_line = ltrim($last_line, '"'); + $last_line = explode('" "', $last_line); + + // Remove Election Name + array_pop($last_line); + + foreach ($last_line as &$oneCandidate) { + $oneCandidate = str_replace('"', '', $oneCandidate); + $oneCandidate = new Candidate($oneCandidate); + } + + $this->candidates = $last_line; + } + + protected function readVotes(): void + { + // Remove last two lines + array_pop($this->lines); // Last + array_pop($this->lines); // New Last + + // Remove first line + array_shift($this->lines); + + // Read each line + foreach ($this->lines as &$oneVote) { + $oneVote = explode(' ', $oneVote); + + // Remove first line + array_shift($oneVote); + // Remove Last line + array_pop($oneVote); + + foreach ($oneVote as &$oneRank) { + $oneRank = (int) $oneRank; + $oneRank = $this->candidates[$oneRank - 1]; + } + } + } +} diff --git a/include/Condorcet/src/Tools/Converters/DebianFormat.php b/include/Condorcet/src/Tools/Converters/DebianFormat.php new file mode 100644 index 0000000..f273550 --- /dev/null +++ b/include/Condorcet/src/Tools/Converters/DebianFormat.php @@ -0,0 +1,114 @@ +lines = file($filePath, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES); + + $this->readCandidatesNames(); + $this->readVotes(); + } + + #[PublicAPI] + #[Description('Add the Debian data to an election object')] + #[FunctionReturn('The election object')] + #[Related("Tools\CondorcetElectionFormat::setDataToAnElection", "Tools\DavidHillFormat::setDataToAnElection")] + public function setDataToAnElection( + #[FunctionParameter('Add an existing election, useful if you want to set up some parameters or add extra candidates. If null an election object will be created for you.')] + ?Election $election = null + ): Election { + if ($election === null) { + $election = new Election; + $election->setNumberOfSeats(1); + } + + foreach ($this->candidates as $oneCandidate) { + $election->addCandidate($oneCandidate); + } + + foreach ($this->votes as $oneVote) { + $election->addVote($oneVote); + } + + return $election; + } + + // Internal + + protected function readCandidatesNames(): void + { + $pattern = '/^.*Option [1-9].*: (?.*)$/m'; + + $candidatesLines = preg_grep($pattern, $this->lines); + + $candidates = []; + // $k = 1; + + foreach ($candidatesLines as $oneCandidateLine) { + $match = null; + preg_match($pattern, $oneCandidateLine, $match); + + // $candidates[$k++] = new Candidate( \trim($match['candidateName']) ); + // $candidates[] = new Candidate( \trim( $match['candidateName']) ); + $candidateName = $match['candidateName']; + mb_check_encoding($candidateName) || ($candidateName = mb_convert_encoding($candidateName, 'UTF-8', 'ISO-8859-16')); + $candidateName = trim($candidateName); + + $candidates[] = new Candidate($candidateName); + } + + $this->candidates = $candidates; + } + + protected function readVotes(): void + { + $pattern = '/^(V:)? ?(?[1-9-]+)[ \t]+(?[a-z0-9]+)$/m'; + + $votesLines = preg_grep($pattern, $this->lines); + $votes = []; + + foreach ($votesLines as $oneVoteLine) { + $match = null; + + preg_match($pattern, trim($oneVoteLine), $match); + $oneVoteLineRanking = mb_str_split($match['Ranking']); + + $oneVote = []; + foreach ($oneVoteLineRanking as $candidateKey => $candidateRankEvaluation) { + if (is_numeric($candidateRankEvaluation)) { + $oneVote[(int) $candidateRankEvaluation][] = $this->candidates[$candidateKey]; + } + } + + $votes[] = new Vote($oneVote, $match['MD5']); + } + + $this->votes = $votes; + } +} diff --git a/include/Condorcet/src/Utils/CondorcetUtil.php b/include/Condorcet/src/Utils/CondorcetUtil.php new file mode 100644 index 0000000..cb03a80 --- /dev/null +++ b/include/Condorcet/src/Utils/CondorcetUtil.php @@ -0,0 +1,109 @@ + &$line) { + // Delete comments + $is_comment = mb_strpos($line, '#'); + if ($is_comment !== false) { + $line = mb_substr($line, 0, $is_comment); + } + + // Trim + $line = trim($line); + + // Remove empty + if (empty($line)) { + unset($input[$key]); + } + } + + return $input; + } + + // Simplify Condorcet Var_Dump. Transform object to String. + #[PublicAPI] + #[Description("Provide pretty re-formatting, human compliant, of all Condorcet PHP object or result set.\nCan be use before a var_dump, or just to get more simple data output.")] + #[FunctionReturn('New formatted data.')] + public static function format( + #[FunctionParameter('Input to convert')] + mixed $input, + #[FunctionParameter('If true. Will convert Candidate objects into string representation of their name')] + bool $convertObject = true + ): mixed { + if (\is_object($input)) { + $r = $input; + + if ($convertObject) { + if ($input instanceof Candidate) { + $r = (string) $input; + } elseif ($input instanceof Vote) { + $r = $input->getSimpleRanking(); + } elseif ($input instanceof Result) { + $r = $input->getResultAsArray(true); + } + } + } elseif (!\is_array($input)) { + $r = $input; + } else { + foreach ($input as $key => $line) { + $input[$key] = self::format($line, $convertObject); + } + + if (\count($input) === 1 && \is_int(key($input)) && (!\is_array(reset($input)) || \count(reset($input)) === 1)) { + $r = reset($input); + } else { + $r = $input; + } + } + + return $r; + } +} diff --git a/include/Condorcet/src/Utils/VoteEntryParser.php b/include/Condorcet/src/Utils/VoteEntryParser.php new file mode 100644 index 0000000..72d0b58 --- /dev/null +++ b/include/Condorcet/src/Utils/VoteEntryParser.php @@ -0,0 +1,142 @@ +originalEntry = $entry; + + // Disallow < and " + if (preg_match('/<|"/mi', $entry) === 1) { + throw new VoteInvalidFormatException("found '<' or '|' in " . $entry); + } + + $this->comment = $this->getComment($entry, true); + + $this->multiple = self::parseIntValueFromVoteStringOffset('*', $entry, true); + $this->weight = self::parseIntValueFromVoteStringOffset('^', $entry, true); + + $this->tags = $this->convertTagsFromVoteString($entry, true); + + $this->ranking = self::convertRankingFromString($entry); + } + + // From a string like 'A>B=C=H>G=T>Q' + public static function convertRankingFromString(string $formula): ?array + { + $formula = trim($formula); + + // Condorcet Election Format special string + if (empty($formula)) { + return null; + } elseif ($formula === CondorcetElectionFormat::SPECIAL_KEYWORD_EMPTY_RANKING) { + return []; + } else { + $ranking = explode('>', $formula); + + foreach ($ranking as &$rank_vote) { + $rank_vote = explode('=', $rank_vote); + $rank_vote = array_filter($rank_vote); + + // Del space at start and end + foreach ($rank_vote as &$value) { + $value = trim($value); + } + } + + return array_filter($ranking); + } + } + + public static function convertTagsFromVoteString(string &$voteString, bool $cut = false): ?array + { + $offset = mb_strpos($voteString, '||'); + + if (\is_int($offset)) { + $tagsPart = mb_substr($voteString, 0, $offset); + + $tags = explode(',', $tagsPart); + + array_walk($tags, static function (string &$value): void { + $value = trim($value); + }); + + $cut && $voteString = mb_substr($voteString, $offset + 2); + + return $tags; + } else { + return null; + } + } + + public static function getComment(string &$voteString, bool $cut = false): ?string + { + $offset = mb_strpos($voteString, '#'); + + if (\is_int($offset)) { + $comment = trim(mb_substr($voteString, $offset + 1)); + + $cut && $voteString = mb_substr($voteString, 0, $offset); + + return $comment; + } else { + return null; + } + } + + #[Throws(VoteInvalidFormatException::class)] + public static function parseIntValueFromVoteStringOffset(string $character, string &$entry, bool $cut = false): int + { + $offset = mb_strpos($entry, $character); + + if (\is_int($offset)) { + $input = trim(mb_substr($entry, $offset + 1)); + + $value = ''; + + foreach (mb_str_split($input) as $char) { + if (!\in_array($char, ['#', '^', '*', "\n"], true)) { + $value .= $char; + } else { + break; + } + } + + // Errors + if (!is_numeric($value)) { + throw new VoteInvalidFormatException("the value '{$value}' is not an integer."); + } + + $cut && $entry = mb_substr($entry, 0, $offset); + + $value = \intval($value); + return ($value > 0) ? $value : 1; + } else { + return 1; + } + } +} diff --git a/include/Condorcet/src/Utils/VoteUtil.php b/include/Condorcet/src/Utils/VoteUtil.php new file mode 100644 index 0000000..1aab248 --- /dev/null +++ b/include/Condorcet/src/Utils/VoteUtil.php @@ -0,0 +1,57 @@ + trim($x), $tags); + + foreach ($tags as $tag) { + if (empty($tag)) { + throw new VoteInvalidFormatException('found empty tag'); + } + } + + return $tags; + } + + public static function getRankingAsString(array $ranking): string + { + foreach ($ranking as &$rank) { + if (\is_array($rank)) { + sort($rank); + $rank = implode(' = ', $rank); + } + } + + return implode(' > ', $ranking); + } +} diff --git a/include/Condorcet/src/Vote.php b/include/Condorcet/src/Vote.php new file mode 100644 index 0000000..066c242 --- /dev/null +++ b/include/Condorcet/src/Vote.php @@ -0,0 +1,680 @@ +position = 1; + } + + public function current(): array + { + return $this->getRanking()[$this->position]; + } + + public function key(): int + { + return $this->position; + } + + public function next(): void + { + ++$this->position; + } + + public function valid(): bool + { + return isset($this->getRanking()[$this->position]); + } + + // Vote + + private array $ranking; + + private float $lastTimestamp; + + private int $counter; + + private array $ranking_history = []; + + private int $weight = 1; + + private array $tags = []; + + private string $hashCode = ''; + + private ?Election $electionContext = null; + + public bool $notUpdate = false; + + + // Performance (for internal use) + protected static ?\stdClass $cacheKey = null; + protected ?\WeakMap $cacheMap = null; + + public static function initCache(): \stdClass + { + self::$cacheKey = new \stdClass; + return self::$cacheKey; + } + + public static function clearCache(): void + { + self::$cacheKey = null; + } + + // ------- + + #[PublicAPI] + #[Description('Build a vote object.')] + #[Throws(VoteInvalidFormatException::class)] + #[Example('Manual - Add Vote', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote')] + #[Related('Vote::setRanking', 'Vote::addTags')] + public function __construct( + #[FunctionParameter('Equivalent to Vote::setRanking method')] + array|string $ranking, + #[FunctionParameter('Equivalent to Vote::addTags method')] + array|string|null $tags = null, + #[FunctionParameter('Set your own timestamp metadata on Ranking')] + ?float $ownTimestamp = null, + #[FunctionParameter('Try to convert directly your candidates from sting input" to Candidate object of one election')] + ?Election $electionContext = null + ) { + $this->cacheMap = new \WeakMap; + + $this->electionContext = $electionContext; + $tagsFromString = null; + + // Vote Weight + if (\is_string($ranking)) { + $parsedVote = new VoteEntryParser($ranking); + + if ($parsedVote->weight > 1) { + $weight = $parsedVote->weight; + } + + $tagsFromString = $parsedVote->tags; + + $ranking = $parsedVote->ranking ?? []; + } + + $this->setRanking($ranking, $ownTimestamp); + $tags === null || $this->addTags($tags); + $tagsFromString === null || $this->addTags($tagsFromString); + + if (isset($weight)) { + $this->setWeight($weight); + } + + $this->electionContext = null; + } + + public function __serialize(): array + { + $this->position = 1; + $this->link = null; + + $var = get_object_vars($this); + unset($var['cacheMap']); + + return $var; + } + + public function __wakeup(): void + { + $this->cacheMap = new \WeakMap; + } + + public function __clone(): void + { + $this->destroyAllLink(); + $this->computeHashCode(); + } + + public function __toString(): string + { + if (empty($this->getTags())) { + return $this->getSimpleRanking(); + } else { + return $this->getTagsAsString().' || '.$this->getSimpleRanking(); + } + } + + #[PublicAPI] + #[Description('Get Object hash (cryptographic)')] + #[FunctionReturn('SHA hash code.')] + #[Related('Vote::getWeight')] + public function getHashCode(): string + { + return $this->hashCode; + } + + // ------- + + // GETTERS + + #[PublicAPI] + #[Description('Get the actual Ranking of this Vote.')] + #[FunctionReturn('Multidimenssionnal array populated by Candidate object.')] + #[Related('Vote::setRanking')] + public function getRanking( + #[FunctionParameter('Sort Candidate in a Rank by name. Useful for performant internal calls from methods.')] + bool $sortCandidatesInRank = true + ): array { + $r = $this->ranking; + + foreach ($r as &$oneRank) { + if ($sortCandidatesInRank && \count($oneRank) > 1) { + sort($oneRank, \SORT_STRING); + } + } + + return $r; + } + + #[PublicAPI] + #[Description('Return an history of each vote change, with timestamp.')] + #[FunctionReturn('An explicit multi-dimenssional array.')] + #[Related('Vote::getCreateTimestamp')] + public function getHistory(): array + { + return $this->ranking_history; + } + + + #[PublicAPI] + #[Description('Get the registered tags for this Vote.')] + #[FunctionReturn('List of registered tag.')] + #[Related('Vote::getTagsAsString', 'Vote::addTags', 'Vote::removeTags')] + public function getTags(): array + { + return $this->tags; + } + + #[PublicAPI] + #[Description('Get the registered tags for this Vote.')] + #[FunctionReturn('List of registered tag as string separated by commas.')] + #[Related('Vote::getTags', 'Vote::addTags', 'Vote::removeTags')] + public function getTagsAsString(): string + { + return implode(',', $this->getTags()); + } + + #[PublicAPI] + #[Description('Get the timestamp corresponding of the creation of this vote.')] + #[FunctionReturn('Timestamp')] + #[Related('Candidate::getTimestamp')] + public function getCreateTimestamp(): float + { + return $this->ranking_history[0]['timestamp']; + } + + #[PublicAPI] + #[Description('Get the timestamp corresponding of the last vote change.')] + #[FunctionReturn('Timestamp')] + #[Related('Vote::getCreateTimestamp')] + public function getTimestamp(): float + { + return $this->lastTimestamp; + } + + #[PublicAPI] + #[Description('Count the number of candidate provide into the active Ranking set.')] + #[FunctionReturn('Number of Candidate into ranking.')] + public function countRankingCandidates(): int + { + return $this->counter; + } + + #[PublicAPI] + #[Description('Get all the candidates object set in the last ranking of this Vote.')] + #[FunctionReturn('Candidates list.')] + #[Related('Vote::getRanking', 'Vote::countRankingCandidates')] + public function getAllCandidates(): array + { + $list = []; + + foreach ($this->getRanking(false) as $rank) { + foreach ($rank as $oneCandidate) { + $list[] = $oneCandidate; + } + } + + return $list; + } + + #[PublicAPI] + #[Description('Return the vote actual ranking complete for the contexte of the provide election. Election must be linked to the Vote object.')] + #[FunctionReturn('Contextual full ranking.')] + #[Throws(VoteNotLinkedException::class)] + #[Related('Vote::getContextualRankingAsString', 'Vote::getRanking')] + public function getContextualRanking( + #[FunctionParameter('An election already linked to the Vote')] + Election $election, + ): array { + return $this->computeContextualRanking($election, true); + } + + // Performances + #[InternalModulesAPI] + public function getContextualRankingWithoutSort( + #[FunctionParameter('An election already linked to the Vote')] + Election $election, + ): array { + return $this->computeContextualRanking($election, false); + } + + protected function computeContextualRanking( + #[FunctionParameter('An election already linked to the Vote')] + Election $election, + #[FunctionParameter('If false, performance can be increased for Implicit Ranking election.')] + bool $sortLastRankByName + ): array { + // Cache for internal use + if (self::$cacheKey !== null && !$sortLastRankByName && $this->cacheMap->offsetExists(self::$cacheKey)) { + return $this->cacheMap->offsetGet(self::$cacheKey); + } + + // Normal procedure + if (!$this->haveLink($election)) { + throw new VoteNotLinkedException; + } + + $countContextualCandidate = 0; + + $present = $this->getAllCandidates(); + $candidates_list = $election->getCandidatesList(); + $candidates_count = $election->countCandidates(); + + $newRanking = $this->computeContextualRankingWithoutImplicit($this->getRanking(false), $election, $countContextualCandidate); + + if ($election->getImplicitRankingRule() && $countContextualCandidate < $candidates_count) { + $last_rank = []; + $needed = $candidates_count - $countContextualCandidate; + + foreach ($candidates_list as $oneCandidate) { + if (!\in_array(needle: $oneCandidate, haystack: $present, strict: true)) { + $last_rank[] = $oneCandidate; + } + + if (\count($last_rank) === $needed) { + break; + } + } + + if ($sortLastRankByName) { + sort($last_rank, \SORT_STRING); + } + + $newRanking[\count($newRanking) + 1] = $last_rank; + } + + // Cache for internal use + if (self::$cacheKey !== null && !$sortLastRankByName) { + $this->cacheMap->offsetSet(self::$cacheKey, $newRanking); + } + + return $newRanking; + } + + protected function computeContextualRankingWithoutImplicit(array $ranking, Election $election, int &$countContextualCandidate = 0): array + { + $newRanking = []; + $nextRank = 1; + $rankChange = false; + + foreach ($ranking as $CandidatesInRanks) { + foreach ($CandidatesInRanks as $candidate) { + if ($election->isRegisteredCandidate($candidate, true)) { + $newRanking[$nextRank][] = $candidate; + $countContextualCandidate++; + $rankChange = true; + } + } + + if ($rankChange) { + $nextRank++; + $rankChange = false; + } + } + + return $newRanking; + } + + #[PublicAPI] + #[Description('Return the vote actual ranking complete for the contexte of the provide election. Election must be linked to the Vote object.')] + #[FunctionReturn('Contextual full ranking, with string instead Candidate object.')] + #[Related('Vote::getContextualRanking', 'Vote::getRanking')] + public function getContextualRankingAsString( + #[FunctionParameter('An election already linked to the Vote')] + Election $election + ): array { + return CondorcetUtil::format($this->getContextualRanking($election), true); + } + + #[PublicAPI] + #[Description('Get the current ranking as a string format. Optionally with an election context, see Election::getContextualRanking()')] + #[FunctionReturn("String like 'A>D=C>B'")] + #[Related('Vote::getRanking')] + public function getSimpleRanking( + #[FunctionParameter('An election already linked to the Vote')] + ?Election $context = null, + #[FunctionParameter('Include or not the weight symbol and value')] + bool $displayWeight = true + ): string { + $ranking = $context ? $this->getContextualRanking($context) : $this->getRanking(); + + $simpleRanking = VoteUtil::getRankingAsString($ranking); + + if ($displayWeight && $this->weight > 1 && (($context && $context->isVoteWeightAllowed()) || $context === null)) { + $simpleRanking .= ' ^'.$this->getWeight(); + } + + return $simpleRanking; + } + + + // SETTERS + + #[PublicAPI] + #[Description("Set a new ranking for this vote.\n\nNote that if your vote is already linked to one ore more elections, your ranking must be compliant with all of them, else an exception is throw. For do this, you need to use only valid Candidate object, you can't register a new ranking from string if your vote is already linked to an election.")] + #[FunctionReturn('In case of success, return TRUE')] + #[Throws(VoteInvalidFormatException::class)] + #[Example('Manual - Add a vote', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote')] + #[Related('Vote::getRanking', 'Vote::getHistory', 'Vote::__construct')] + public function setRanking( + #[FunctionParameter('A Ranking. Have a look at the Wiki https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote to learn the available ranking formats.')] + array|string $ranking, + #[FunctionParameter('Set your own timestamp metadata on Ranking. Your timestamp must be > than last registered timestamp. Else, an exception will be throw.')] + ?float $ownTimestamp = null + ): bool { + // Timestamp + if ($ownTimestamp !== null) { + if (!empty($this->ranking_history) && $this->getTimestamp() >= $ownTimestamp) { + throw new VoteInvalidFormatException('Timestamp format of vote is not correct'); + } + } + + // Ranking + $candidateCounter = $this->formatRanking($ranking); + + if ($this->electionContext !== null) { + $this->electionContext->convertRankingCandidates($ranking); + } + + if (!$this->notUpdate) { + foreach ($this->getLinks() as $link => $value) { + $link->prepareUpdateVote($this); + } + } + + $this->ranking = $ranking; + $this->lastTimestamp = $ownTimestamp ?? microtime(true); + $this->counter = $candidateCounter; + + $this->archiveRanking(); + + if (\count($this->link) > 0) { + try { + foreach ($this->getLinks() as $link => $value) { + if (!$link->checkVoteCandidate($this)) { + throw new VoteInvalidFormatException('vote does not match candidate in this election'); + } + } + } catch (VoteInvalidFormatException $e) { + foreach ($this->getLinks() as $link => $value) { + $link->setStateToVote(); + } + + throw $e; + } + + if (!$this->notUpdate) { + foreach ($this->getLinks() as $link => $value) { + $link->finishUpdateVote($this); + } + } + } + + $this->computeHashCode(); + return true; + } + + private function formatRanking(array|string &$ranking): int + { + if (\is_string($ranking)) { + $ranking = (new VoteEntryParser($ranking))->ranking ?? []; + } + + $ranking = array_filter($ranking, static fn ($key): bool => is_numeric($key), \ARRAY_FILTER_USE_KEY); + + ksort($ranking); + + $i = 1; + $vote_r = []; + foreach ($ranking as &$value) { + if (!\is_array($value)) { + $vote_r[$i] = [$value]; + } else { + $vote_r[$i] = $value; + } + + $i++; + } + + $ranking = $vote_r; + + $counter = 0; + $list_candidate = []; + foreach ($ranking as &$line) { + foreach ($line as &$Candidate) { + if (!($Candidate instanceof Candidate)) { + $Candidate = new Candidate($Candidate); + $Candidate->setProvisionalState(true); + } + + $counter++; + + // Check Duplicate + + // Check objet reference AND check candidates name + if (!\in_array($name = $Candidate->getName(), $list_candidate, true)) { + $list_candidate[] = $name; + } else { + throw new VoteInvalidFormatException; + } + } + } + + return $counter; + } + + + #[PublicAPI] + #[Description('Remove candidate from ranking. Set a new ranking and archive the old ranking.')] + #[FunctionReturn('True on success.')] + #[Throws(CandidateDoesNotExistException::class)] + #[Related('Vote::setRanking')] + public function removeCandidate( + #[FunctionParameter('Candidate object or string')] + Candidate|string $candidate + ): bool { + if ($candidate instanceof Candidate) { + $strict = true; + } else { + $strict = false; + } + + $ranking = $this->getRanking(); + + $rankingCandidate = $this->getAllCandidates(); + + if (!\in_array(needle: $candidate, haystack: $rankingCandidate, strict: $strict)) { + throw new CandidateDoesNotExistException((string) $candidate); + } + + foreach ($ranking as $rankingKey => &$rank) { + foreach ($rank as $oneRankKey => $oneRankValue) { + if ($strict ? $oneRankValue === $candidate : $oneRankValue == $candidate) { + unset($rank[$oneRankKey]); + } + } + + if (empty($rank)) { + unset($ranking[$rankingKey]); + } + } + + $this->setRanking($ranking); + + return true; + } + + + #[PublicAPI] + #[Description('Add tag(s) on this Vote.')] + #[FunctionReturn('In case of success, return TRUE')] + #[Throws(VoteInvalidFormatException::class)] + #[Example('Manual - Add Vote', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote')] + #[Related('Vote::removeTags')] + public function addTags( + #[FunctionParameter('Tag(s) are non-numeric alphanumeric string. They can be added by string separated by commas or an array.')] + array|string $tags + ): bool { + $tags = VoteUtil::tagsConvert($tags) ?? []; + + foreach ($tags as $key => $tag) { + if (\in_array(needle: $tag, haystack: $this->tags, strict: true)) { + unset($tags[$key]); + } + } + + foreach ($tags as $tag) { + $this->tags[] = $tag; + } + + $this->computeHashCode(); + + return true; + } + + #[PublicAPI] + #[Description('Remove registered tag(s) on this Vote.')] + #[FunctionReturn('List of deleted tags.')] + #[Example('Manual - Add Vote', 'https://github.com/julien-boudry/Condorcet/wiki/II-%23-B.-Vote-management-%23-1.-Add-Vote')] + #[Related('Vote::addTags')] + public function removeTags( + #[FunctionParameter('They can be added by string separated by commas or an array.')] + array|string $tags + ): array { + $tags = VoteUtil::tagsConvert($tags); + + if (empty($tags)) { + return []; + } + + $rm = []; + foreach ($tags as $key => $tag) { + $tagK = array_search(needle: $tag, haystack: $this->tags, strict: true); + + if ($tagK === false) { + unset($tags[$key]); + } else { + $rm[] = $this->tags[$tagK]; + unset($this->tags[$tagK]); + } + } + + $this->computeHashCode(); + return $rm; + } + + #[PublicAPI] + #[Description('Remove all registered tag(s) on this Vote.')] + #[FunctionReturn('Return True.')] + #[Related('Vote::addTags', 'Vote::removeTags')] + public function removeAllTags(): bool + { + $this->removeTags($this->getTags()); + return true; + } + + #[PublicAPI] + #[Description('Get the vote weight. The vote weight capacity must be active at the election level for producing effect on the result.')] + #[FunctionReturn('Weight. Default weight is 1.')] + #[Related('Vote::setWeight')] + public function getWeight( + #[FunctionParameter('In the context of wich election? (optional)')] + ?Election $context = null + ): int { + if ($context !== null && !$context->isVoteWeightAllowed()) { + return 1; + } else { + return $this->weight; + } + } + + #[PublicAPI] + #[Description('Set a vote weight. The vote weight capacity must be active at the election level for producing effect on the result.')] + #[FunctionReturn('New weight.')] + #[Throws(VoteInvalidFormatException::class)] + #[Related('Vote::getWeight')] + public function setWeight( + #[FunctionParameter('The new vote weight.')] + int $newWeight + ): int { + if ($newWeight < 1) { + throw new VoteInvalidFormatException('the vote weight can not be less than 1'); + } + + if ($newWeight !== $this->weight) { + $this->weight = $newWeight; + + if (\count($this->link) > 0) { + foreach ($this->getLinks() as $link => $value) { + $link->setStateToVote(); + } + } + } + + $this->computeHashCode(); + + return $this->getWeight(); + } + + /////////// INTERNAL /////////// + + private function archiveRanking(): void + { + $this->ranking_history[] = ['ranking' => $this->ranking, 'timestamp' => $this->lastTimestamp, 'counter' => $this->counter]; + + $this->rewind(); + } + + private function computeHashCode(): string + { + return $this->hashCode = hash('sha224', ((string) $this) . microtime(false)); + } +} diff --git a/include/Condorcet/src/VoteConstraintInterface.php b/include/Condorcet/src/VoteConstraintInterface.php new file mode 100644 index 0000000..7c02e4f --- /dev/null +++ b/include/Condorcet/src/VoteConstraintInterface.php @@ -0,0 +1,17 @@ +