-
tomaszrudowski authoredtomaszrudowski authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
APIController.php 46.59 KiB
<?php
namespace App\Http\Controllers;
use App\DelegationOneVote;
use App\Election;
use App\ExtensionDelegationElection;
use App\MajorityVote;
use App\Population;
use App\Voter;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class APIController extends Controller
{
public function generatePopulationTemplate(Request $request)
{
try {
$attributes = $request->validate([
'name' => 'nullable|string',
'size_a' => 'required|integer|min:0|max:1000',
'size_b' => 'required|integer|min:0|max:1000',
'init_expertise_a' => 'required|integer|min:0|max:100',
'init_expertise_b' => 'required|integer|min:0|max:100',
'spread_expertise_a' => 'required|integer|min:0|max:100',
'spread_expertise_b' => 'required|integer|min:0|max:100',
'init_confidence_a' => 'required|integer|min:0|max:100',
'init_confidence_b' => 'required|integer|min:0|max:100',
'spread_confidence_a' => 'required|integer|min:0|max:100',
'spread_confidence_b' => 'required|integer|min:0|max:100',
'init_following_a' => 'required|integer|min:0|max:100',
'init_following_b' => 'required|integer|min:0|max:100',
'spread_following_a' => 'required|integer|min:0|max:100',
'spread_following_b' => 'required|integer|min:0|max:100',
'init_leadership_a' => 'required|integer|min:0|max:100',
'init_leadership_b' => 'required|integer|min:0|max:100',
'spread_leadership_a' => 'required|integer|min:0|max:100',
'spread_leadership_b' => 'required|integer|min:0|max:100',
'forgetting_factor' => 'nullable|int|min:0|max:100',
'follower_factor' => 'nullable|min:1'
]);
} catch (ValidationException $e) {
return response()->json([
'message' => 'Incorrect payload',
'val_errors' => $e->errors()
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$start = microtime(true);
$population = new Population();
$forgettingFactor = Config::get('app.forgetting_factor', 0.99);
if (isset($attributes['forgetting_factor'])) {
$forgettingFactor = 1 - 0.01 * $attributes['forgetting_factor'];
}
$population->forgetting_factor = $forgettingFactor;
$population->follower_factor = isset($attributes['follower_factor']) ? $attributes['follower_factor'] : Config::get('app.follower_factor', 100);
$population->save();
$population->name = isset($attributes['name']) ? $attributes['name'] : 'Population Template ' . $population->id;
$population->update();
$minValue = 1;
$maxValue = 100;
$minExpertiseValueA = $attributes['init_expertise_a'] - $attributes['spread_expertise_a'];
$minExpertiseValueA = $minExpertiseValueA < $minValue ? $minValue : $minExpertiseValueA;
$maxExpertiseValueA = $attributes['init_expertise_a'] + $attributes['spread_expertise_a'];
$maxExpertiseValueA = $maxExpertiseValueA > $maxValue ? $maxValue : $maxExpertiseValueA;
$minExpertiseValueB = $attributes['init_expertise_b'] - $attributes['spread_expertise_b'];
$minExpertiseValueB = $minExpertiseValueB < $minValue ? $minValue : $minExpertiseValueB;
$maxExpertiseValueB = $attributes['init_expertise_b'] + $attributes['spread_expertise_b'];
$maxExpertiseValueB = $maxExpertiseValueB > $maxValue ? $maxValue : $maxExpertiseValueB;
$minConfidenceValueA = $attributes['init_confidence_a'] - $attributes['spread_confidence_a'];
$minConfidenceValueA = $minConfidenceValueA < $minValue ? $minValue : $minConfidenceValueA;
$maxConfidenceValueA = $attributes['init_confidence_a'] + $attributes['spread_confidence_a'];
$maxConfidenceValueA = $maxConfidenceValueA > $maxValue ? $maxValue : $maxConfidenceValueA;
$minConfidenceValueB = $attributes['init_confidence_b'] - $attributes['spread_confidence_b'];
$minConfidenceValueB = $minConfidenceValueB < $minValue ? $minValue : $minConfidenceValueB;
$maxConfidenceValueB = $attributes['init_confidence_b'] + $attributes['spread_confidence_b'];
$maxConfidenceValueB = $maxConfidenceValueB > $maxValue ? $maxValue : $maxConfidenceValueB;
$minFollowingValueA = $attributes['init_following_a'] - $attributes['spread_following_a'];
$minFollowingValueA = $minFollowingValueA < $minValue ? $minValue : $minFollowingValueA;
$maxFollowingValueA = $attributes['init_following_a'] + $attributes['spread_following_a'];
$maxFollowingValueA = $maxFollowingValueA > $maxValue ? $maxValue : $maxFollowingValueA;
$minFollowingValueB = $attributes['init_following_b'] - $attributes['spread_following_b'];
$minFollowingValueB = $minFollowingValueB < $minValue ? $minValue : $minFollowingValueB;
$maxFollowingValueB = $attributes['init_following_b'] + $attributes['spread_following_b'];
$maxFollowingValueB = $maxFollowingValueB > $maxValue ? $maxValue : $maxFollowingValueB;
$minLeadershipValueA = $attributes['init_leadership_a'] - $attributes['spread_leadership_a'];
$minLeadershipValueA = $minLeadershipValueA < $minValue ? $minValue : $minLeadershipValueA;
$maxLeadershipValueA = $attributes['init_leadership_a'] + $attributes['spread_leadership_a'];
$maxLeadershipValueA = $maxLeadershipValueA > $maxValue ? $maxValue : $maxLeadershipValueA;
$minLeadershipValueB = $attributes['init_leadership_b'] - $attributes['spread_leadership_b'];
$minLeadershipValueB = $minLeadershipValueB < $minValue ? $minValue : $minLeadershipValueB;
$maxLeadershipValueB = $attributes['init_leadership_b'] + $attributes['spread_leadership_b'];
$maxLeadershipValueB = $maxLeadershipValueB > $maxValue ? $maxValue : $maxLeadershipValueB;
$data = new \stdClass();
$data->meta = new \stdClass();
$data->meta->size_a = $attributes['size_a'];
$data->meta->min_expertise_a = $minExpertiseValueA;
$data->meta->max_expertise_a = $maxExpertiseValueA;
$data->meta->min_confidence_a = $minConfidenceValueA;
$data->meta->max_confidence_a = $maxConfidenceValueA;
$data->meta->min_following_a = $minFollowingValueA;
$data->meta->max_following_a = $maxFollowingValueA;
$data->meta->min_leadership_a = $minLeadershipValueA;
$data->meta->max_leadership_a = $maxLeadershipValueA;
$data->meta->size_b = $attributes['size_b'];
$data->meta->min_expertise_b = $minExpertiseValueB;
$data->meta->max_expertise_b = $maxExpertiseValueB;
$data->meta->min_confidence_b = $minConfidenceValueB;
$data->meta->max_confidence_b = $maxConfidenceValueB;
$data->meta->min_following_b = $minFollowingValueB;
$data->meta->max_following_b = $maxFollowingValueB;
$data->meta->min_leadership_b = $minLeadershipValueB;
$data->meta->max_leadership_b = $maxLeadershipValueB;
/*
$data->random_expertise_array_a = array();
$data->random_expertise_array_b = array();
$data->random_confidence_array_a = array();
$data->random_confidence_array_b = array();
$data->random_following_array_a = array();
$data->random_following_array_b = array();
for ($i = $minValue; $i <= $maxValue; $i++) {
$data->random_expertise_array_a[$i] = 0;
$data->random_expertise_array_b[$i] = 0;
$data->random_confidence_array_a[$i] = 0;
$data->random_confidence_array_b[$i] = 0;
$data->random_following_array_a[$i] = 0;
$data->random_following_array_b[$i] = 0;
}
*/
$data->meta->init_vars = microtime(true) - $start;
for ($i = 0; $i < $attributes['size_a']; $i++) {
$randomExpertiseValue = random_int($minExpertiseValueA, $maxExpertiseValueA);
//$data->random_expertise_array_a[$randomExpertiseValue]++;
$randomConfidenceValue = random_int($minConfidenceValueA, $maxConfidenceValueA);
//$data->random_confidence_array_a[$randomConfidenceValue]++;
$randomFollowingValue = random_int($minFollowingValueA, $maxFollowingValueA);
//$data->random_following_array_a[$randomFollowingValue]++;
$randomLeadershipValue = random_int($minLeadershipValueA, $maxLeadershipValueA);
Voter::create([
'population_id' => $population->id,
'expertise' => $randomExpertiseValue,
'confidence' => $randomConfidenceValue,
'following' => $randomFollowingValue,
'leadership' => $randomLeadershipValue,
'group' => 'A'
]);
}
$data->meta->done_group_a = microtime(true) - $data->meta->init_vars;
for ($i = 0; $i < $attributes['size_b']; $i++) {
$randomExpertiseValue = random_int($minExpertiseValueB, $maxExpertiseValueB);
//$data->random_expertise_array_b[$randomExpertiseValue]++;
$randomConfidenceValue = random_int($minConfidenceValueB, $maxConfidenceValueB);
//$data->random_confidence_array_b[$randomConfidenceValue]++;
$randomFollowingValue = random_int($minFollowingValueB, $maxFollowingValueB);
//$data->random_following_array_b[$randomFollowingValue]++;
$randomLeadershipValue = random_int($minLeadershipValueB, $maxLeadershipValueB);
Voter::create([
'population_id' => $population->id,
'expertise' => $randomExpertiseValue,
'confidence' => $randomConfidenceValue,
'following' => $randomFollowingValue,
'leadership' => $randomLeadershipValue,
'group' => 'B'
]);
}
$data->meta->done_group_b = microtime(true) - $data->meta->done_group_a;
$data->meta->total_time = round(microtime(true) - $start, 3);
return response()->json($data, Response::HTTP_OK);
}
public function getChildPopulations(Population $population) {
$data = Population::with('voters', 'elections')
->where('parent_id', '=', $population->id)
->orderBy('id', 'asc')
->get()
->makeHidden(['elections', 'voters']);
$data->each(function($item) {
$item->append(['elections_stats', 'voters_stats']);
});
return response()->json($data, Response::HTTP_OK);
}
public function generateChildPopulation(Population $template, Request $request) {
try {
$attributes = $request->validate([
'election_type' => 'required|string|in:m,d1,d2,d3'
]);
} catch (ValidationException $e) {
return response()->json([
'message' => 'Incorrect payload',
'val_errors' => $e->errors()
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$nextChildId = $template->childPopulations()->where('election_type', '=', $attributes['election_type'])->count() + 1;
$newPopulation = new Population();
if (!isset($template->parent_id)) {
$newPopulationName = "[". $template->name . "].(" . $attributes['election_type'] . "-" . $nextChildId . ")";
} else {
$newPopulationName = $template->name . ".(" . $attributes['election_type'] . "-" . $nextChildId . ")";
}
$newPopulation->election_type = $attributes['election_type'];
$newPopulation->name = $newPopulationName;
$newPopulation->parent_id = $template->id;
$newPopulation->forgetting_factor = $template->forgetting_factor;
$newPopulation->follower_factor = $template->follower_factor;
$newPopulation->stage = $newPopulation->election_type === "d1" ? 'p' : 'l'; // 'performance'/'learning' default stage for child population
$newPopulation->save();
foreach ($template->voters as $parentPopulationVoter) {
Voter::create([
'population_id' => $newPopulation->id,
'expertise' => $parentPopulationVoter->expertise,
'confidence' => $parentPopulationVoter->confidence,
'following' => $parentPopulationVoter->following,
'leadership' => $parentPopulationVoter->leadership,
'reputation' => $parentPopulationVoter->reputation,
'group' => $parentPopulationVoter->group
]);
}
}
public function getPopulationTemplates(Request $request)
{
$data = Population::with('voters', 'elections', 'childPopulations')
->whereNull('parent_id')
->orderBy('id', 'desc')
->get()
->makeHidden(['elections', 'voters', 'childPopulations']);
$data->each(function($item) {
$item->append(['elections_stats', 'voters_stats', 'children_count', 'child_populations_stats']);
});
return response()->json($data, Response::HTTP_OK);
}
public function getPopulationTemplate(int $template, Request $request) {
$data = Population::where('id', '=', $template)
->whereNull('parent_id')
->with('elections', 'voters', 'childPopulations')
->firstOrFail()
->append(['elections_stats', 'voters_stats', 'children_count', 'child_populations_stats'])
->makeHidden('elections', 'voters');
$data->childPopulations->each(function($item) {
$item->append(['elections_stats']);
});
return response()->json($data, Response::HTTP_OK);
}
public function getPopulation(int $template, int $population, Request $request)
{
$attributes = $request->validate([
'omit_voters' => 'nullable|boolean'
]);
$omitVoters = isset($attributes["omit_voters"]) ? $attributes["omit_voters"] : false;
$toAppend = $omitVoters ? ['elections_stats'] : ['elections_stats', 'voters_stats'];
$data = Population::where('id', '=', $population)
->where('parent_id', '=', $template)
->with('elections')
->when(!$omitVoters, function($q) {
$q->with('voters');
})
->firstOrFail()
->append($toAppend)
->makeHidden('elections', 'voters');
$data->forgetting_percent = 100 - (100 * Config::get('app.forgetting_factor', 0));
$data->follower_factor = Config::get('app.follower_factor');
return response()->json($data, Response::HTTP_OK);
}
public function changeToPerformanceStage(int $template, int $population, Request $request)
{
$childPopulation = Population::where('id', '=', $population)
->where('parent_id', '=', $template)
->firstOrFail();
$childPopulation->stage = 'p';
if ($childPopulation->save()) {
return response()->json(null, Response::HTTP_NO_CONTENT);
}
return response()->json(['error' => 'Stage not saved'], Response::HTTP_INTERNAL_SERVER_ERROR);
}
public function getMajorityElectionsDistribution(int $template, int $population, Request $request)
{
$metadata = new \stdClass();
$startTime = microtime(true);
$populationData = Population::where('id', '=', $population)
->with(['elections' => function($q) {
$q->where('type', '=', 'm');
}])
->firstOrFail();
$distribution = array();
$distributionRounded10 = array();
//$raw = array();
for($i=0;$i<101;$i++){
$distribution[$i] = 0;
}
for($i=0;$i<11;$i++){
$distributionRounded10[$i] = 0;
}
foreach ($populationData->elections as $election) {
$sum = $election->total_correct + $election->total_incorrect;
if ($sum > 0) {
$percentCorrect = $election->total_correct / $sum;
$distribution[floor(100 * $percentCorrect)]++;
$distributionRounded10[floor(10 * $percentCorrect)]++;
//$raw[] = round($percentCorrect, 2);
}
}
$distributionRounded10[9] = $distributionRounded10[9] + $distributionRounded10[10];
unset($distributionRounded10[10]);
//$sortedRaw = sort($raw);
$metadata->total_time = round(microtime(true) - $startTime, 3);
$metadata->number_of_elections = $populationData->elections->count();
return response()->json([
'metadata' => $metadata,
//'sorted_raw' => $raw,
'distribution' => $distribution,
'distribution_r_10' => $distributionRounded10
], Response::HTTP_OK);
}
public function getVoters(int $template, int $population, Request $request)
{
$data = Population::where('id', '=', $population)
->with('voters')
->firstOrFail();
return response()->json($data->voters, Response::HTTP_OK);
}
/**
* @deprecated
*
* @param $population
* @param Request $request
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function runMajorityElections(int $template, int $population, Request $request) {
$data = new \stdClass();
$data->elections_stats = array();
$attributes = $request->validate([
'number' => 'nullable|integer|min:1'
]);
$numberOfElections = isset($attributes['number']) ? $attributes['number'] : 1;
$existingPopulation = Population::where('id', '=', $population)
->with('voters')
->firstOrFail();
$startTime = microtime(true);
for( $i = 0; $i < $numberOfElections; $i++) {
$singleElectionStats = $this->runSingleMajorityElection($existingPopulation);
$data->elections[] = $singleElectionStats;
}
$data->total_time = round(microtime(true) - $startTime, 3);
$data->number_of_elections = $numberOfElections;
return response()->json($data, Response::HTTP_OK);
}
/**
* @param $population
* @param null $forgettingFactor
* @param null $followerFactor
* @return \stdClass
* @throws \Exception
*/
private function runSingleMajorityElection($population, $forgettingFactor = null, $followerFactor = null): \stdClass
{
$startTime = microtime(true);
$election = Election::create([
'population_id' => $population->id,
'type' => 'm'
]);
$minValue = 1;
$maxValue = 100;
$votes = array();
$electionStats = new \stdClass();
$electionStats->type = "m";
$correctChoices = 0;
$incorrectChoices = 0;
foreach ($population->voters as $voter) {
$random = random_int($minValue, $maxValue);
$vote = $random <= $voter->expertise;
$vote ? $correctChoices++ : $incorrectChoices++;
$votes[] = [
'election_id' => $election->id,
'voter_id' => $voter->id,
'vote' => $vote
];
}
$votesTime = microtime(true);
$electionStats->votes_time = round($votesTime - $startTime, 3);
MajorityVote::insert($votes);
$electionStats->total_correct_choices = round($correctChoices, 2);
$electionStats->total_incorrect_choices = round($incorrectChoices, 2);
$sum = $correctChoices + $incorrectChoices;
if ($sum > 0) {
$percentCorrect = 100 * $correctChoices / $sum;
} else {
$percentCorrect = null;
}
$electionStats->percent_correct_choices = round($percentCorrect, 2);
$election->total_correct = $correctChoices;
$election->total_incorrect = $incorrectChoices;
$election->save();
$dbTime = microtime(true);
$electionStats->votes_db_time = round($dbTime - $votesTime,3);
return $electionStats;
}
public function runElections(int $template, int $population, Request $request) {
$startTime = microtime(true);
$data = new \stdClass();
//$data->elections_stats = array();
$attributes = $request->validate([
'type' => 'required|string',
'number' => 'nullable|integer|min:1',
'omit_details' => 'nullable|boolean'
]);
$omitElectionDetails = isset($attributes['omit_details']) && $attributes['omit_details'];
$numberOfElections = isset($attributes['number']) ? $attributes['number'] : 1;
switch ($attributes['type']) {
case 'm' :
$electionMethod = 'runSingleMajorityElection';
break;
case 'd1' :
$electionMethod = 'runSingleDelegationElectionVersion1';
break;
case 'd2' :
$electionMethod = 'runSingleDelegationElectionVersion2';
break;
case 'd3' :
$electionMethod = 'runSingleDelegationElectionVersion3';
break;
default :
return response()->json('unknown election type', Response::HTTP_UNPROCESSABLE_ENTITY);
}
$existingPopulation = Population::where('id', '=', $population)
->where('parent_id', '=', $template)
->with('voters')
->firstOrFail();
$data->population_name = $existingPopulation->name;
$data->forgetting_factor = $existingPopulation->forgetting_factor;
$forgettingModifier = 1 - (0.01 * $existingPopulation->forgetting_factor);
$data->follower_factor = $existingPopulation->follower_factor;
$data->number_of_elections = $numberOfElections;
$data->elections_type = $attributes['type'];
$elections = array();
for( $i = 0; $i < $numberOfElections; $i++) {
$singleElectionStats = $this->$electionMethod($existingPopulation, $forgettingModifier, $data->follower_factor);
$elections[] = $singleElectionStats;
}
if(!$omitElectionDetails) {
$data->elections = $elections;
}
$data->total_time = round(microtime(true) - $startTime, 3);
return response()->json($data, Response::HTTP_OK);
}
/**
* Performance stage
* No changes to voters' attributes
*
* @param Population $population
* @param $forgettingModifier
* @param $followerFactor
* @return \stdClass
* @throws \Exception
*/
private function runSingleDelegationElectionVersion1(Population $population, $forgettingModifier, $followerFactor) {
return $this->runSingleDelegationElection($population, false, 'd1', false, $forgettingModifier, $followerFactor);
}
/**
* Learning stage.
* Modify Reputation only after each election
*
* @param Population $population
* @param $forgettingModifier
* @param $followerFactor
* @return \stdClass
* @throws \Exception
*/
private function runSingleDelegationElectionVersion2 (Population $population, $forgettingModifier, $followerFactor) {
return $this->runSingleDelegationElection($population, true, 'd2', false, $forgettingModifier, $followerFactor);
}
/**
* Learning stage.
* Modify Reputation, Following and Leadership after each election
*
* @param Population $population
* @param $forgettingModifier
* @param $followerFactor
* @return \stdClass
* @throws \Exception
*/
private function runSingleDelegationElectionVersion3 (Population $population, $forgettingModifier, $followerFactor) {
return $this->runSingleDelegationElection($population, true, 'd3', true, $forgettingModifier, $followerFactor);
}
/**
* Run delegation election, default in performance stage (d1)
* @param Population $population
* @param bool $modifyReputation
* @param string $type
* @param bool $modifyAttributes
* @param $forgettingModifier
* @param $followerFactor
* @return \stdClass
* @throws \Exception
*/
private function runSingleDelegationElection(
Population $population,
$modifyReputation = false,
$type = 'd1',
$modifyAttributes = false,
$forgettingModifier,
$followerFactor
) {
$startTime = microtime(true);
$election = Election::create([
'population_id' => $population->id,
'type' => $type
]);
$votes = array();
$electionStats = new \stdClass();
$electionStats->type = "d2"; // for chart
$asDelegate = 0;
$asFollower = 0;
$asIndependent = 0;
$weightedDelegatesIDs = array();
$lastInsertedWeight = 0;
$followersDistribution = array();
$minValue = 1;
$maxValue = 100;
$correctChoices = 0;
$incorrectChoices = 0;
$noOfVotes = $population->voters->count();
$electionStats->no_of_votes = $noOfVotes;
$followersVotes = [];
$votingWeightA = 0;
$votingWeightB = 0;
$sumReputationA = 0;
$sumReputationB = 0;
foreach ($population->voters as $voter) {
$voterWeight = $voter->reputation > 0 ? $voter->reputation : 1;
if ($voter->group == "A") {
$votingWeightA += $voterWeight;
$sumReputationA += $voter->reputation;
} else {
$votingWeightB += $voterWeight;
$sumReputationB+= $voter->reputation;
}
$tresholdFollowing = $voter->following;
$tresholdIndependent = $tresholdFollowing + $voter->confidence;
$tresholdLeadership = $tresholdIndependent + $voter->leadership;
$randomBehaviour = random_int(0, $tresholdLeadership);
$randomExpertise = random_int($minValue, $maxValue);
$voteDirect = $randomExpertise <= $voter->expertise;
$voteDirect ? $correctChoices++ : $incorrectChoices++;
$newVote = [
'election_id' => $election->id,
'voter_id' => $voter->id,
'vote_direct' => $voteDirect,
'vote_delegation' => null,
'vote_weight' => 1,
'vote_final' => $voteDirect
];
if ($randomBehaviour <= $tresholdFollowing) {
$newVote['voter_mark'] = 'f';
$asFollower++;
$followersVotes[$voter->id] = $newVote;
} elseif (($randomBehaviour <= $tresholdIndependent)) {
$newVote['voter_mark'] = 'i';
$asIndependent++;
$votes[$voter->id] = $newVote;
} else {
$newVote['voter_mark'] = 'd';
$asDelegate++;
$reputationWeight = $voter->reputation > 0 ? $voter->reputation : 1; // minimum weight = 1
$weightedDelegatesIDs[$voter->id] = $lastInsertedWeight + $reputationWeight;
$lastInsertedWeight = $weightedDelegatesIDs[$voter->id];
$followersDistribution[$voter->id] = 0;
$votes[$voter->id] = $newVote;
}
}
$electionStats->initial_correct = $correctChoices;
$electionStats->initial_incorrect = $incorrectChoices;
$prepareTime = microtime(true);
DelegationOneVote::insert($votes);
$delegatesSavedVotes = array();
// replace own expertise test if follower and there are delegates
if ($asDelegate > 0) {
$savedVotes = DelegationOneVote::where('election_id', '=', $election->id)
->where('voter_mark', '=', 'd')
->get();
foreach ($savedVotes as $savedVote) {
$delegatesSavedVotes[$savedVote->voter_id] = $savedVote;
}
foreach ($followersVotes as $key => $item) {
if($item['voter_mark'] == 'f' ) {
// choose delegate
$randomDelegation = random_int(1, $lastInsertedWeight);
$delegateID = $this->findDelegateID($weightedDelegatesIDs, $randomDelegation);
$followersVotes[$key]['vote_delegation'] = $delegatesSavedVotes[$delegateID]->id;//$delegateID;
$followersDistribution[$delegateID]++;
// revert expertise choice
$item['vote_direct'] ? $correctChoices-- : $incorrectChoices--;
//$followersVotes[$key]['vote_direct'] = null; // keep track of own expertise choice
$followersVotes[$key]['vote_weight'] = 0;
// add delegate choice
$delegatesSavedVotes[$delegateID]->vote_weight++;
$delegatesSavedVotes[$delegateID]->vote_direct ? $correctChoices++ : $incorrectChoices++;
$followersVotes[$key]['vote_final'] = $delegatesSavedVotes[$delegateID]->vote_final;
}
}
}
// Adjust attributes of all voters (skip leadership/following adjustments)
// if in learning stage
if ($population->stage === 'l') {
foreach ($population->voters as $voter) {
$previousReputation = $voter->reputation;
$voterID = $voter->id;
if (isset($delegatesSavedVotes[$voterID])) {
// is delegate
$noOfFollowers = $followersDistribution[$delegateID];
if ($noOfFollowers > 0) {
// save weight adjustments for delegate's vote
$delegatesSavedVotes[$voterID]->save();
// adjust voter reputation
if ($delegatesSavedVotes[$voterID]->vote_final > 0) {
$voter->reputation += ($followerFactor * $noOfFollowers);
if($modifyAttributes && $voter->leadership < 100)
$voter->leadership++;
} else {
$voter->reputation -= ($followerFactor * $noOfFollowers);
if($modifyAttributes && $voter->leadership > 1)
$voter->leadership--;
}
} else {
if($modifyAttributes && $voter->leadership > 1)
$voter->leadership--;
}
} elseif ($modifyAttributes && isset($followersVotes[$voterID])) {
// is follower
if ($followersVotes[$voterID]['vote_final']) {
if (!$followersVotes[$voterID]['vote_direct']) {
if($voter->following < 100)
$voter->following++;
}
} elseif ($followersVotes[$voterID]['vote_direct']) {
if($voter->following > 1)
$voter->following--;
}
}
// balance voter reputation over time between elections
$voter->reputation = round($voter->reputation * $forgettingModifier);
/*if ($voter->reputation < 0) {
$voter->reputation++;
} elseif ($voter->reputation > 0) {
$voter->reputation--;
}*/
if ($previousReputation != $voter->reputation) {
$voter->save();
}
}
}
$election->total_correct = $correctChoices;
$election->total_incorrect = $incorrectChoices;
$electionStats->total_correct_choices = $correctChoices;
$electionStats->total_incorrect_choices = $incorrectChoices;
if ($noOfVotes > 0) {
$electionStats->percent_initial_correct_choices = 100 * $electionStats->initial_correct / $noOfVotes;
$electionStats->percent_correct_choices = 100 * $election->total_correct / $noOfVotes;
} else {
$electionStats->percent_initial_correct_choices = null;
$electionStats->percent_correct_choices = null;
}
$votesTime = microtime(true);
$election->save();
$electionExtension = ExtensionDelegationElection::create([
'election_id' => $election->id,
'initial_correct' => $electionStats->initial_correct,
'initial_incorrect' => $electionStats->initial_incorrect,
'as_delegate' => $asDelegate,
'as_follower' => $asFollower,
'as_independent' => $asIndependent,
'weight_a' => $votingWeightA,
'weight_b' => $votingWeightB,
'reputation_a' => $sumReputationA,
'reputation_b' => $sumReputationB
]);
DelegationOneVote::insert($followersVotes);
$databaseTime = microtime(true);
$electionStats->votes_time = round($votesTime - $startTime, 5);
$electionStats->votes_db_time = round($databaseTime - $votesTime, 5);
$electionStats->as_delegate = $asDelegate;
$electionStats->as_follower = $asFollower;
$electionStats->as_independent = $asIndependent;
$electionStats->delegates = array_keys($weightedDelegatesIDs);
$electionStats->cumulative_delegates_reputation = $weightedDelegatesIDs;
$electionStats->followers_distribution = $followersDistribution;
$electionStats->data = array_values($votes);
return $electionStats;
}
private function findDelegateID(array $weightedDelegatesIDs, int $randomDelegation) {
foreach ($weightedDelegatesIDs as $id => $cumulativeReputation) {
if ($randomDelegation <= $cumulativeReputation) {
return $id;
}
}
return null;
}
public function getVoterStats(int $template, int $population, $voter) {
$data = Voter::where('id', '=', $voter)
->with('delegationOneVotes.parentDelegationOneVote')
->firstOrFail()
->makeHidden('delegationOneVotes')
;
return response()->json($data,200);
}
public function getVotersStats(int $template, int $population) {
$population = Population::where('id', '=', $population)
->with('voters.delegationOneVotes')
->firstOrFail();
$population->voters->makeHidden('delegationOneVotes');
return response()->json($population->voters,200);
}
public function getElectionsTimeline (int $template, Population $population, Request $request) {
$data = new \stdClass();
try {
$attributes = $request->validate([
'type' => 'required|string|in:m,d1,d2,d3',
'moving_average' => 'nullable|integer'
]);
} catch (ValidationException $e) {
return response()->json(['error' => 'Unknown election type'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$movingAverage = isset($attributes['moving_average']) ? $attributes['moving_average'] : 0;
$data->elections_type = $attributes['type'];
$data->no_of_voters = $population->noOfVoters;
if ($data->no_of_voters < 1) {
return response()->json(['error' => 'No voters in population'], Response::HTTP_NOT_FOUND);
}
$elections = Election::where('population_id', '=', $population->id)
->where('type', '=', $attributes['type'])
->select(
DB::raw(sprintf('100 * total_correct / %d AS percent', $data->no_of_voters))
)
->pluck('percent');
$data->no_of_elections = $elections->count();
$data->moving_average = $movingAverage;
if($movingAverage > 0) {
$flattenData = [];
if ($data->no_of_elections >= $movingAverage) {
$asArray = $elections->toArray();
for ($i = $movingAverage - 1; $i < $data->no_of_elections; $i++) {
$sumOfValues = 0;
for ($j = 0; $j < $movingAverage; $j++) {
$sumOfValues += $asArray[$i - $j];
}
$flattenData[] = $sumOfValues / $movingAverage;
}
}
$data->elections = $flattenData;
} else {
$data->elections = $elections;
}
return response()->json($data, Response::HTTP_OK);
}
public function getWeightsTimeline (int $template, Population $population, Request $request) {
$data = new \stdClass();
try {
$attributes = $request->validate([
'type' => 'required|string|in:d2,d3',
'moving_average' => 'nullable|integer'
]);
} catch (ValidationException $e) {
return response()->json(['error' => 'Unknown election type'], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$movingAverage = isset($attributes['moving_average']) ? $attributes['moving_average'] : 0;
$data->elections_type = $attributes['type'];
$countA = $population->voters()->where('group', '=', 'A')->count();
$countB = $population->voters()->where('group', '=', 'B')->count();
$data->no_of_voters = $countA + $countB;
if ($data->no_of_voters < 1) {
return response()->json(['error' => 'No voters in population'], Response::HTTP_NOT_FOUND);
}
$data->no_of_voters_a = $countA;
$data->no_of_voters_b = $countB;
$elections = Election::where('population_id', '=', $population->id)
->where('type', '=', $attributes['type'])
->with('extension')->get();
$data->no_of_elections = $elections->count();
$data->moving_average = $movingAverage;
$weightsTimeline = array();
foreach ($elections as $election) {
$weightSum = $election->extension->weight_a + $election->extension->weight_b;
$newWeight = new \stdClass();
if ($countA > 0) {
$newWeight->reputation_a = $election->extension->reputation_a;
$newWeight->avg_weight_a = $election->extension->weight_a / $countA;
} else {
$newWeight->reputation_a = 0;
$newWeight->avg_weight_a = 0;
}
if ($countB > 0) {
$newWeight->reputation_b = $election->extension->reputation_b;
$newWeight->avg_weight_b = $election->extension->weight_b / $countB;
//$newWeight->weight_share_b = 100 * $newWeight->avg_weight_b / ($newWeight->avg_weight_a + $newWeight->avg_weight_b);
$newWeight->weight_share_b = 100 * $election->extension->weight_b / ($election->extension->weight_a + $election->extension->weight_b);
} else {
$newWeight->reputation_b = 0;
$newWeight->avg_weight_b = 0;
$newWeight->weight_share_b = 0;
}
array_push($weightsTimeline, $newWeight);
}
$data->weights = $weightsTimeline;
/*
if($movingAverage > 0) {
$flattenData = [];
if ($data->no_of_elections >= $movingAverage) {
$asArray = $elections->toArray();
for ($i = $movingAverage - 1; $i < $data->no_of_elections; $i++) {
$sumOfValues = 0;
for ($j = 0; $j < $movingAverage; $j++) {
$sumOfValues += $asArray[$i - $j];
}
$flattenData[] = $sumOfValues / $movingAverage;
}
}
$data->elections = $flattenData;
} else {
$data->elections = $elections;
}*/
return response()->json($data, Response::HTTP_OK);
}
public function getTemplateWeightsStats(int $template, Request $request) {
$startTime = microtime(true);
$metadata = new \stdClass();
try {
$attributes = $request->validate([
'election_type' => 'required|string|in:d2,d3'
]);
} catch (ValidationException $e) {
return response()->json([
'message' => 'Incorrect payload',
'val_errors' => $e->errors()
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$type = $attributes['election_type'];
$templatePopulation = Population::where('id', '=', $template)
->whereNull('parent_id')
->with(['childPopulations' => function ($q) use ($type) {
$q->where('election_type', '=', $type)
->with('elections.extension');
}])
->firstOrFail()
->makeHidden('childPopulations');
$templatePopulation->report_metadata = $metadata;
$countA = $templatePopulation->voters()->where('group', '=', 'A')->count();
$countB = $templatePopulation->voters()->where('group', '=', 'B')->count();
$templatePopulation->no_of_voters = $countA + $countB;
if ($templatePopulation->no_of_voters < 1) {
return response()->json(['error' => 'No voters in population'], Response::HTTP_NOT_FOUND);
}
$templatePopulation->no_of_voters_a = $countA;
$templatePopulation->no_of_voters_b = $countB;
$templatePopulation->no_of_child_populations = $templatePopulation->childPopulations->count();
$first = true;
$minElectionCount = 0;
$maxElectionCount = 0;
$childPopulationCount = 0;
foreach ($templatePopulation->childPopulations as $childPopulation) {
$childPopulationCount++;
$electionCount = $childPopulation->elections()->count();
if ($first) {
$minElectionCount = $electionCount;
$maxElectionCount = $electionCount;
$first = false;
} else {
if ($electionCount > $maxElectionCount) {
$maxElectionCount = $electionCount;
}
if ($electionCount < $minElectionCount) {
$minElectionCount = $electionCount;
}
}
$childPopulation->elections_count = $electionCount;
$childPopulation->makeHidden('elections');
}
$templatePopulation->min_election_count = $minElectionCount;
$templatePopulation->max_election_count = $maxElectionCount;
// init weight counters
$weightsSequencesA = array();
$weightsSequencesB = array();
$weightShareSequenceB = array();
$minWeightShareSequenceB = array();
$maxWeightShareSequenceB = array();
$minWeightsSequencesA = array();
$minWeightsSequencesB = array();
$maxWeightsSequencesA = array();
$maxWeightsSequencesB = array();
for ($i = 0; $i < $minElectionCount; $i++) {
$weightsSequencesA[$i] = 0;
$weightsSequencesB[$i] = 0;
}
$first = true;
foreach ($templatePopulation->childPopulations as $childPopulation) {
$childPopulation->exceeding_elections = $childPopulation->elections_count - $minElectionCount;
if ($first) {
for ($i = 0; $i < $minElectionCount; $i++) {
$weightA = $childPopulation->elections[$i]->extension->weight_a;
$weightB = $childPopulation->elections[$i]->extension->weight_b;
$weightShareB = $weightB / ($weightA + $weightB);
$weightsSequencesA[$i] = $weightA;
$weightsSequencesB[$i] = $weightB;
$minWeightsSequencesA[$i] = $weightA;
$minWeightsSequencesB[$i] = $weightB;
$maxWeightsSequencesA[$i] = $weightA;
$maxWeightsSequencesB[$i] = $weightB;
$weightShareSequenceB[$i] = $weightShareB;
$minWeightShareSequenceB[$i] = $weightShareB;
$maxWeightShareSequenceB[$i] = $weightShareB;
}
$first = false;
continue;
}
for ($i = 0; $i < $minElectionCount; $i++) {
$weightA = $childPopulation->elections[$i]->extension->weight_a;
$weightB = $childPopulation->elections[$i]->extension->weight_b;
$weightShareB = $weightB / ($weightA + $weightB);
$weightsSequencesA[$i] += $weightA;
$weightsSequencesB[$i] += $weightB;
if ($minWeightsSequencesA[$i] > $weightA) {
$minWeightsSequencesA[$i] = $weightA;
}
if ($minWeightsSequencesB[$i] > $weightB) {
$minWeightsSequencesB[$i] = $weightB;
}
if ($weightA > $maxWeightsSequencesA[$i]) {
$maxWeightsSequencesA[$i] = $weightA;
}
if ($weightB > $maxWeightsSequencesB[$i]) {
$maxWeightsSequencesB[$i] = $weightB;
}
if ($weightShareB > $maxWeightShareSequenceB[$i]) {
$maxWeightShareSequenceB[$i] = $weightShareB;
}
if ($minWeightShareSequenceB[$i] > $weightShareB) {
$minWeightShareSequenceB[$i] = $weightShareB;
}
}
}
$weightsAvgTimeline = array();
//$votersFactorA = $templatePopulation->no_of_child_populations * $countA;
//$votersFactorB = $templatePopulation->no_of_child_populations * $countB;
for ($i = 0; $i < $minElectionCount; $i++) {
$newWeight = new \stdClass();
$newWeight->min_weight_a = $minWeightsSequencesA[$i];
$newWeight->max_weight_a = $maxWeightsSequencesA[$i];
$newWeight->min_weight_b = $minWeightsSequencesB[$i];
$newWeight->max_weight_b = $maxWeightsSequencesB[$i];
//$newWeight->avg_voter_weight_a = $weightsSequencesA[$i] / $votersFactorA;
//$newWeight->avg_voter_weight_b = $weightsSequencesB[$i] / $votersFactorB;
$newWeight->avg_weight_a = $weightsSequencesA[$i] / $templatePopulation->no_of_child_populations;
$newWeight->avg_weight_b = $weightsSequencesB[$i] / $templatePopulation->no_of_child_populations;
$newWeight->share_sum_weight_b = $weightShareSequenceB[$i];
$newWeight->min_share_weight_b = $minWeightShareSequenceB[$i];
$newWeight->max_share_weight_b = $maxWeightShareSequenceB[$i];
array_push($weightsAvgTimeline, $newWeight);
}
$templatePopulation->weights_timeline = $weightsAvgTimeline;
$endTime = microtime(true);
$metadata->process_time = round($endTime - $startTime, 5);
return response()->json($templatePopulation, Response::HTTP_OK);
}
}