<?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\Support\Facades\Storage; 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(); $population->forgetting_factor = isset($attributes['forgetting_factor']) ? $attributes['forgetting_factor'] : Config::get('app.forgetting_factor', 1); $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 { //TEMP $newPopulationName = $template->name . ".(g2-" . $nextChildId . ")"; //$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; // 'performance'/'learning' default stage for child population if($newPopulation->election_type === "d1" || $newPopulation->election_type === "m") { $newPopulation->stage = 'p'; } else { $newPopulation->stage = 'l'; } $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) { $attributes = $request->validate([ 'top_level_only' => 'nullable|boolean' ]); $topLevelOnly = isset($attributes["top_level_only"]) ? $attributes["top_level_only"] : true; $data = Population::with('voters', 'elections', 'childPopulations') ->whereNull('parent_id') ->when(!$topLevelOnly,function($q) { $q->orWhere(function($q) { $q->where('stage','=','p') ->where('election_type', '<>', 'd1'); }); }) //->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) ->where(function($q) { $q->whereNull('parent_id') ->orWhere(function($q) { $q->where('stage','=','p') ->where('election_type', '<>', 'd1'); }); }) ->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; $existingPopulation = Population::where('id', '=', $population) ->where('parent_id', '=', $template) ->with('voters') ->firstOrFail(); switch ($attributes['type']) { case 'm' : $electionMethod = 'runSingleMajorityElection'; break; case 'd1' : $electionMethod = 'runSingleDelegationElectionVersion1'; break; case 'd2' : $electionMethod = $existingPopulation->stage == 'l' ? 'runSingleDelegationElectionVersion2' : 'runSingleDelegationElectionVersion1'; break; case 'd3' : $electionMethod = $existingPopulation->stage == 'l' ? 'runSingleDelegationElectionVersion3' : 'runSingleDelegationElectionVersion1'; break; default : return response()->json('unknown election type', Response::HTTP_UNPROCESSABLE_ENTITY); } $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']; $metadata->election_type = $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; $weightShareSequenceB[$i] += $weightShareB; 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; $previousWeightShareB = 0; for ($i = 0; $i < $minElectionCount; $i++) { $newWeight = new \stdClass(); $newWeight->min_sum_weight_a = $minWeightsSequencesA[$i]; $newWeight->max_sum_weight_a = $maxWeightsSequencesA[$i]; $newWeight->min_sum_weight_b = $minWeightsSequencesB[$i]; $newWeight->max_sum_weight_b = $maxWeightsSequencesB[$i]; //$newWeight->avg_voter_weight_a = $weightsSequencesA[$i] / $votersFactorA; //$newWeight->avg_voter_weight_b = $weightsSequencesB[$i] / $votersFactorB; $newWeight->avg_sum_weight_a = $weightsSequencesA[$i] / $templatePopulation->no_of_child_populations; $newWeight->avg_sum_weight_b = $weightsSequencesB[$i] / $templatePopulation->no_of_child_populations; $newWeight->share_sum_weight_b = $weightShareSequenceB[$i] / $templatePopulation->no_of_child_populations; $newWeight->min_share_sum_weight_b = $minWeightShareSequenceB[$i]; $newWeight->max_share_sum_weight_b = $maxWeightShareSequenceB[$i]; $shareDiff = $newWeight->share_sum_weight_b - $previousWeightShareB; $newWeight->diff_share_sum_weight_b = $shareDiff > 0 ? $shareDiff : -$shareDiff; $previousWeightShareB = $newWeight->share_sum_weight_b; array_push($weightsAvgTimeline, $newWeight); } $templatePopulation->weights = $weightsAvgTimeline; $endTime = microtime(true); $metadata->process_time = round($endTime - $startTime, 5); return response()->json($templatePopulation, Response::HTTP_OK); } public function fetchAllPerformanceModeElections(int $template, Request $request) { $startTime = microtime(true); $metadata = new \stdClass(); $templatePopulation = Population::where('id', '=', $template) //->whereNull('parent_id') ->with(['childPopulations' => function ($q){ $q->where('stage', '=', 'p') ->with(['elections' => function ($q1) { $q1->where('type', '=', 'd1') ->with('extension'); }]); }]) ->firstOrFail() ->makeHidden('childPopulations'); $d2TypesCount = 0; $d2Correct = 0; $d2Incorrect = 0; $d3TypesCount = 0; $d3Correct = 0; $d3Incorrect = 0; foreach ($templatePopulation->childPopulations as $childPopulation) { $electionCount = $childPopulation->elections->count(); $totalCorrect = $childPopulation->elections->sum('total_correct'); $totalIncorrect = $childPopulation->elections->sum('total_incorrect'); if ($childPopulation->election_type == 'd2') { $d2TypesCount += $electionCount; $d2Correct += $totalCorrect; $d2Incorrect += $totalIncorrect; } elseif($childPopulation->election_type == 'd2') { $d3TypesCount += $electionCount; $d3Correct += $totalCorrect; $d3Incorrect += $totalIncorrect; } } $response = new \stdClass(); $response->report_metadata = $metadata; $response->results = array(); $d2Votes = $d2Correct + $d2Incorrect; if ($d2Votes > 0) { $d2Stats = new \stdClass(); $d2Stats->election_type = 'd2'; $d2Stats->election_count = $d2TypesCount; $d2Stats->correct = $d2Correct; $d2Stats->incorrect = $d2Incorrect; $d2Stats->percent_correct = 100 * $d2Correct / $d2Votes; $response->results['d2'] = $d2Stats; } else { $response->results['d2'] = null; } $d3Votes = $d3Correct + $d3Incorrect; if ($d3Votes > 0) { $d3Stats = new \stdClass(); $d3Stats->election_type = 'd3'; $d3Stats->election_count = $d3TypesCount; $d3Stats->correct = $d3Correct; $d3Stats->incorrect = $d3Incorrect; $d3Stats->percent_correct = 100 * $d2Correct / $d3Votes; $response->results['d3'] = $d3Stats; } else { $response->results['d3'] = null; } $endTime = microtime(true); $metadata->process_time = round($endTime - $startTime, 5); return response()->json($response, Response::HTTP_OK); } /** * Gets all performance mode results for d1,d2,d3 * * @param int $template * @param Request $request * @return \Illuminate\Http\JsonResponse */ public function getTemplateElectionResultStats(int $template, Request $request) { $startTime = microtime(true); $metadata = new \stdClass(); try { $attributes = $request->validate([ 'election_type' => 'required|string|in:d1,d2,d3' ]); } catch (ValidationException $e) { return response()->json([ 'message' => 'Incorrect payload', 'val_errors' => $e->errors() ], Response::HTTP_UNPROCESSABLE_ENTITY); } $type = $attributes['election_type']; $metadata->election_type = $type; $templatePopulation = Population::where('id', '=', $template) //->whereNull('parent_id') ->with(['childPopulations' => function ($q) use ($type) { $q->where('election_type', '=', $type) ->where('stage', '=', 'p') ->with(['elections' => function ($q1) { $q1->where('type', '=', 'd1') ->with('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; $templatePopulation->avg_expertise = $templatePopulation->voters()->average('expertise'); $templatePopulation->max_expertise = $templatePopulation->voters()->max('expertise'); 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; $noumberOfVoters = $templatePopulation->no_of_voters; $electionResultsGathered = array(); 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'); foreach ($childPopulation->elections as $election) { $correct = $election->total_correct; $correctShare = $correct / $noumberOfVoters; $correctPercent = 100 * $correctShare; array_push($electionResultsGathered, $correctPercent); } } $templatePopulation->min_election_count = $minElectionCount; $templatePopulation->max_election_count = $maxElectionCount; sort($electionResultsGathered); $templatePopulation->all_elections_gathered = $electionResultsGathered; $logPath = 'election_logs/' . $templatePopulation->id . '_' . time() . '.json'; Storage::disk('local')->put($logPath, json_encode($electionResultsGathered)); $endTime = microtime(true); $metadata->process_time = round($endTime - $startTime, 5); return response()->json($templatePopulation, Response::HTTP_OK); $electionResults = array(); $electionResultsDetails = array(); $electionResultsGathered = array(); for ($i = 0; $i < $minElectionCount; $i++) { $electionResults[$i] = 0; } $distributionGathered = array(); $distributionGatheredRounded5 = array(); $distributionGatheredRounded10 = array(); for($i=0;$i<101;$i++){ $distributionGathered[$i] = 0; } for($i=0;$i<21;$i++){ $distributionGatheredRounded5[$i] = 0; } for($i=0;$i<11;$i++){ $distributionGatheredRounded10[$i] = 0; } foreach ($templatePopulation->childPopulations as $childPopulation) { $childPopulation->exceeding_elections = $childPopulation->elections_count - $minElectionCount; $childResults = array(); for ($i = 0; $i < $minElectionCount; $i++) { $correct = $childPopulation->elections[$i]->total_correct; $correctShare = $correct / $templatePopulation->no_of_voters; $correctPercent = 100 * $correctShare; $electionResults[$i] += $correctPercent; array_push($childResults, $correctPercent); array_push($electionResultsGathered, $correctPercent); $distributionGathered[floor($correctPercent)]++; $distributionGatheredRounded5[floor(20 * $correctShare)]++; $distributionGatheredRounded10[floor(10 * $correctShare)]++; } array_push($electionResultsDetails, $childResults); } /* $electionsAvgTimeline = array(); for ($i = 0; $i < $minElectionCount; $i++) { $newResult = $electionResults[$i] / $templatePopulation->no_of_child_populations; array_push($electionsAvgTimeline, $newResult); } */ //sort($electionsAvgTimeline); //$templatePopulation->elections_avg = $electionsAvgTimeline; //$templatePopulation->elections_detailed = $electionResultsDetails; sort($electionResultsGathered); $templatePopulation->all_elections_gathered = $electionResultsGathered; //$templatePopulation->elections_gathered_distribution = $distributionGathered; //$templatePopulation->elections_gathered_distribution_5 = $distributionGatheredRounded5; //$templatePopulation->elections_gathered_distribution_10 = $distributionGatheredRounded10; $endTime = microtime(true); $metadata->process_time = round($endTime - $startTime, 5); return response()->json($templatePopulation, Response::HTTP_OK); } }