Skip to content
Snippets Groups Projects
APIController.php 57.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • <?php
    
    namespace App\Http\Controllers;
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    use App\DelegationOneVote;
    
    use App\Election;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    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;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    use Illuminate\Support\Facades\DB;
    
    use Illuminate\Support\Facades\Storage;
    
    use Illuminate\Validation\ValidationException;
    
    class APIController extends Controller
    {
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        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();
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $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);
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        public function getChildPopulations(Population $population) {
    
            $data = Population::with('voters', 'elections')
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                ->where('parent_id', '=', $population->id)
                ->orderBy('id', 'asc')
    
                ->get()
                ->makeHidden(['elections', 'voters']);
    
            $data->each(function($item) {
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                $item->append(['elections_stats', 'voters_stats']);
    
            });
    
            return response()->json($data, Response::HTTP_OK);
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        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 . ")";
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            }
            $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';
            }
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $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;
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $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')
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                ->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');
                        });
                })
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                ->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)
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                ->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);
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        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);
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        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);
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        /**
         * @deprecated
         *
         * @param $population
         * @param Request $request
         * @return \Illuminate\Http\JsonResponse
         * @throws \Exception
         */
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        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);
        }
    
        /**
    
    tomaszrudowski's avatar
    tomaszrudowski committed
         * @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([
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                'population_id' => $population->id,
    
                'type' => 'm'
            ]);
    
            $minValue = 1;
            $maxValue = 100;
            $votes = array();
            $electionStats = new \stdClass();
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $electionStats->type = "m";
    
            $correctChoices = 0;
            $incorrectChoices = 0;
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            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;
        }
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        public function runElections(int $template, int $population, Request $request) {
    
    
            $startTime = microtime(true);
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $data = new \stdClass();
    
            //$data->elections_stats = array();
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
            $attributes = $request->validate([
                'type'      => 'required|string',
    
                'number'    => 'nullable|integer|min:1',
                'omit_details' => 'nullable|boolean'
    
            $omitElectionDetails = isset($attributes['omit_details']) && $attributes['omit_details'];
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $numberOfElections = isset($attributes['number']) ? $attributes['number'] : 1;
    
    
            $existingPopulation = Population::where('id', '=', $population)
                ->where('parent_id', '=', $template)
                ->with('voters')
                ->firstOrFail();
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            switch ($attributes['type']) {
                case 'm' :
                    $electionMethod = 'runSingleMajorityElection';
                    break;
                case 'd1' :
                    $electionMethod = 'runSingleDelegationElectionVersion1';
                    break;
    
                    $electionMethod = $existingPopulation->stage == 'l' ? 'runSingleDelegationElectionVersion2' : 'runSingleDelegationElectionVersion1';
    
                    $electionMethod =  $existingPopulation->stage == 'l' ? 'runSingleDelegationElectionVersion3' : 'runSingleDelegationElectionVersion1';
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                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();
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            for( $i = 0; $i < $numberOfElections; $i++) {
    
                $singleElectionStats = $this->$electionMethod($existingPopulation, $forgettingModifier, $data->follower_factor);
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                $elections[] = $singleElectionStats;
            }
    
    
            if(!$omitElectionDetails) {
                $data->elections = $elections;
            }
    
            $data->total_time = round(microtime(true) - $startTime, 3);
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
            return response()->json($data, Response::HTTP_OK);
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        /**
         * Performance stage
         * No changes to voters' attributes
         *
         * @param Population $population
    
         * @param $forgettingModifier
    
         * @param $followerFactor
    
    tomaszrudowski's avatar
    tomaszrudowski committed
         * @return \stdClass
         * @throws \Exception
         */
    
        private function runSingleDelegationElectionVersion1(Population $population, $forgettingModifier, $followerFactor) {
            return $this->runSingleDelegationElection($population, false, 'd1', false, $forgettingModifier, $followerFactor);
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        }
    
        /**
         * Learning stage.
         * Modify Reputation only after each election
         *
         * @param Population $population
    
         * @param $forgettingModifier
    
         * @param $followerFactor
    
    tomaszrudowski's avatar
    tomaszrudowski committed
         * @return \stdClass
         * @throws \Exception
         */
    
        private function runSingleDelegationElectionVersion2 (Population $population, $forgettingModifier, $followerFactor) {
            return $this->runSingleDelegationElection($population, true, 'd2', false, $forgettingModifier, $followerFactor);
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        }
    
        /**
         * Learning stage.
         * Modify Reputation, Following and Leadership after each election
         *
         * @param Population $population
    
         * @param $forgettingModifier
    
         * @param $followerFactor
    
    tomaszrudowski's avatar
    tomaszrudowski committed
         * @return \stdClass
         * @throws \Exception
         */
    
        private function runSingleDelegationElectionVersion3 (Population $population, $forgettingModifier, $followerFactor) {
            return $this->runSingleDelegationElection($population, true, 'd3', true, $forgettingModifier, $followerFactor);
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        }
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        /**
         * Run delegation election, default in performance stage (d1)
         * @param Population $population
         * @param bool $modifyReputation
         * @param string $type
         * @param bool $modifyAttributes
    
         * @param $forgettingModifier
    
         * @param $followerFactor
    
    tomaszrudowski's avatar
    tomaszrudowski committed
         * @return \stdClass
         * @throws \Exception
         */
        private function runSingleDelegationElection(
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $modifyReputation = false,
            $type = 'd1',
    
            $modifyAttributes = false,
    
            $forgettingModifier,
    
        ) {
            $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 = [];
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $votingWeightA = 0;
            $votingWeightB = 0;
    
            $sumReputationA = 0;
            $sumReputationB = 0;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
    
            foreach ($population->voters as $voter) {
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                $voterWeight = $voter->reputation > 0 ? $voter->reputation : 1;
                if ($voter->group == "A") {
                    $votingWeightA += $voterWeight;
    
                    $sumReputationA += $voter->reputation;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                } else {
                    $votingWeightB += $voterWeight;
    
                    $sumReputationB+= $voter->reputation;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                }
    
                $tresholdFollowing = $voter->following;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                $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;
                    }
                }
    
            }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            // 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);
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                                if($modifyAttributes && $voter->leadership < 100)
                                    $voter->leadership++;
                            } else {
    
                                $voter->reputation -= ($followerFactor * $noOfFollowers);
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                                if($modifyAttributes && $voter->leadership > 1)
                                    $voter->leadership--;
                            }
    
                        } else {
                            if($modifyAttributes && $voter->leadership > 1)
                                $voter->leadership--;
                        }
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                    }  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--;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                    // 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--;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
                    if ($previousReputation != $voter->reputation) {
                        $voter->save();
                    }
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
    
            $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,
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                '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;
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
    
    
        private function findDelegateID(array $weightedDelegatesIDs, int $randomDelegation) {
            foreach ($weightedDelegatesIDs as $id => $cumulativeReputation) {
                if ($randomDelegation <= $cumulativeReputation) {
                    return $id;
                }
            }
            return null;
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        public function getVoterStats(int $template, int $population, $voter) {
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $data = Voter::where('id', '=', $voter)
                ->with('delegationOneVotes.parentDelegationOneVote')
                ->firstOrFail()
                ->makeHidden('delegationOneVotes')
            ;
    
            return response()->json($data,200);
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        public function getVotersStats(int $template, int $population) {
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
            $population = Population::where('id', '=', $population)
                ->with('voters.delegationOneVotes')
                ->firstOrFail();
    
            $population->voters->makeHidden('delegationOneVotes');
    
            return response()->json($population->voters,200);
        }
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        public function getElectionsTimeline (int $template, Population $population, Request $request) {
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $data = new \stdClass();
    
            try {
                $attributes = $request->validate([
    
                    'type' => 'required|string|in:m,d1,d2,d3',
                    'moving_average' => 'nullable|integer'
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                ]);
            } catch (ValidationException $e) {
                return response()->json(['error' => 'Unknown election type'], Response::HTTP_UNPROCESSABLE_ENTITY);
            }
    
    
            $movingAverage = isset($attributes['moving_average']) ? $attributes['moving_average'] : 0;
    
    
    tomaszrudowski's avatar
    tomaszrudowski committed
            $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;
            }
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
            return response()->json($data, Response::HTTP_OK);
        }
    
    tomaszrudowski's avatar
    tomaszrudowski committed
        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;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
            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;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    
            $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;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                $newWeight = new \stdClass();
                if ($countA > 0) {
    
                    $newWeight->reputation_a = $election->extension->reputation_a;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                    $newWeight->avg_weight_a = $election->extension->weight_a / $countA;
                } else {
    
                    $newWeight->reputation_a = 0;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                    $newWeight->avg_weight_a = 0;
                }
                if ($countB > 0) {
    
                    $newWeight->reputation_b = $election->extension->reputation_b;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                    $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);
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                } else {
    
                    $newWeight->reputation_b = 0;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                    $newWeight->avg_weight_b = 0;
    
                    $newWeight->weight_share_b = 0;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
                }
                array_push($weightsTimeline, $newWeight);
            }
    
            $data->weights = $weightsTimeline;
    
    tomaszrudowski's avatar
    tomaszrudowski committed
    /*
            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);