Skip to content

Commit

Permalink
started writing a new algorithm for finding constrained shortest path…
Browse files Browse the repository at this point in the history
…, altered manual allocation form to show teams of users
  • Loading branch information
irinahpe committed Mar 27, 2024
1 parent 645f156 commit 9027fb8
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 5 deletions.
37 changes: 37 additions & 0 deletions classes/ratings_and_allocations_table.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ class ratings_and_allocations_table extends \table_sql {
*/
private $showgroups;

/**
* @var bool if true the table should show a column with the teams of the teamvote grouping.
*/
private $showteams;

/**
* @var bool if true the cells are rendered as radio buttons
*/
Expand Down Expand Up @@ -95,6 +100,7 @@ public function __construct(\mod_ratingallocate_renderer $renderer, $titles, $ra
$this->shownames = true;
// We only show the group column if at least one group is being used in at least one active restriction setting of a choice.
$this->showgroups = !empty($allgroupsofchoices);
$this->showteams = (bool) $this->ratingallocate->get_teamvote_goups();
}

/**
Expand Down Expand Up @@ -181,6 +187,10 @@ public function setup_table($choices, $hidenorating = null, $showallocnecessary
$this->ratingallocate->get_choice_groups($choice->id));
}
}
if ($this->showteams) {
$columns[] = 'teams';
$headers[] = get_string('teams');
}
}

// Setup filter.
Expand Down Expand Up @@ -211,12 +221,20 @@ public function setup_table($choices, $hidenorating = null, $showallocnecessary
$this->define_headers($headers);

// Set additional table settings.
if ($this->showteams) {
//$this->sortable(true, 'teams');
} else {

}
$this->sortable(true, 'lastname');
$tableclasses = 'ratingallocate_ratings_table';
if ($this->showgroups) {
$tableclasses .= ' includegroups';
$this->no_sorting('groups');
}
if ($this->showteams) {
$this->no_sorting('teams');
}
$this->set_attribute('class', $tableclasses);

$this->initialbars(true);
Expand Down Expand Up @@ -327,6 +345,18 @@ private function add_user_ratings_row($user, $userratings, $userallocations) {
}, $groupsofuser);
$row['groups'] = implode(';', $groupnames);
}
if ($this->showteams) {
$teamofuser = array_filter(array_keys($this->ratingallocate->get_teamvote_goups()),
function($groupid) use ($user) {
return groups_is_member($groupid,$user->id);
}
);
$teamname = array_map(function ($team) {
return $team->name;
}, $teamofuser);
// We should only have one team for each user, but we cant ensure that at this point.
$row['teams'] = implode(';', $teamname);
}
}

foreach ($userratings as $choiceid => $userrating) {
Expand Down Expand Up @@ -662,6 +692,10 @@ function($c) {

}

private function sort_by_teams ($teams) {

}

/**
* Sets up the sql statement for querying the table data.
*/
Expand All @@ -673,6 +707,7 @@ public function init_sql() {
$userids = $this->filter_userids($userids);

$sortfields = $this->get_sort_columns();
var_dump($sortfields);
$fields = "u.*";
if ($userids) {
$where = "u.id in (" . implode(",", $userids) . ")";
Expand All @@ -690,6 +725,8 @@ public function init_sql() {
$from .= " LEFT JOIN {ratingallocate_ratings} r$i ON u.id = r$i.userid AND r$i.choiceid = :choiceid$i ";
$fields .= ", r$i.rating as $key";
$params["choiceid$i"] = $id;
} else if (substr($key, 0, 5) == "teams") {
//$from .=
}
}

Expand Down
4 changes: 4 additions & 0 deletions form_manual_allocation.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ public function definition_after_data() {
$mform->setDefault('filtergroup', $filter['groupselect']);
$mform->getElement('filtergroup')->setSelected($filter['groupselect']);

if ($this->ratingallocate->get_teamvote_goups()) {

}

$PAGE->requires->js_call_amd('mod_ratingallocate/radiobuttondeselect', 'init');

// The rest must be done through output buffering due to the way flextable works.
Expand Down
10 changes: 9 additions & 1 deletion locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ private function process_action_give_rating() {
} else if ($status === self::DISTRIBUTION_STATUS_RATING_IN_PROGRESS) {
// Rating is possible...

// Adde votegroup name zu form.

// Suche das richtige Formular nach Strategie.
$strategyform = 'ratingallocate\\' . $this->ratingallocate->strategy . '\\mod_ratingallocate_view_form';

Expand Down Expand Up @@ -1290,8 +1292,14 @@ public function get_teamvote_goups() {

// If voting for users not in groups is not disabled, we have to also consider the users that do not have a group.
if ($this->db->get_field(this_db\ratingallocate::TABLE, 'preventvotenotingroup', ['id' => $this->ratingallocateid]) == 0) {

// Get all users not in a group of the teamvote grouping.
$usersnogroup = array_diff($this->get_raters_in_course(), groups_get_grouping_members($groupingid));
$usersnogroup = array();
foreach ($this->get_raters_in_course() as $rater) {
if (!in_array($rater, groups_get_grouping_members($groupingid))) {
$usersnogroup[] = $rater;
}
}

$groupdata = new stdClass();
$groupdata->courseid = $this->course->id;
Expand Down
183 changes: 180 additions & 3 deletions solver/edmonds-karp.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public function compute_distribution($choicerecords, $ratings, $usercount, $team

} else {

var_dump("Teamvote = true");
$teamcount = count($teamvote);
$sink = $choicecount + $teamcount + 1;

Expand All @@ -79,7 +80,7 @@ public function compute_distribution($choicerecords, $ratings, $usercount, $team
// with Bellman-Ford as search function (see: Edmonds-Karp in Introduction to Algorithms)
// http://stackoverflow.com/questions/6681075/while-loop-in-php-with-assignment-operator
// Look for an augmenting path (a shortest path from the source to the sink).
while ($path = $this->find_shortest_path_bellf($source, $sink)) { // If the function returns null, the while will stop.
while ($path = $this->find_shortest_path_bellf_cspf($source, $sink, $teamvote, $toteamid)) { // If the function returns null, the while will stop.
// Reverse the augmentin path, thereby distributing a user into a group.
$this->augment_flow($path);
unset($path); // Clear up old path.
Expand All @@ -90,14 +91,190 @@ public function compute_distribution($choicerecords, $ratings, $usercount, $team

}

/**
* Find the shortest path with constraint (enough space for all teammembers in choice).
* This is a modified version of the Yen Algorithm for the consstrained shortest path first problem.
*
* @param $from
* @param $to
* @param $teamvote
* @param $toteamid
* @return array|mixed|null array of the nodes in the path, null if no path found.
*/
private function find_shortest_path_bellf_cspf ($from, $to, $teamvote, $toteamid) {

// Find the first shortest path.
$pathcandidates = array();
$pathcandidates[0] = $this->find_shortest_path_bellf($from, $to);

$nopathfound = is_null($pathcandidates[0]);

// Check if the path fulfills our constraint: space in choice left >= teammembers.
$constraintflag = true;
$foundedge = null;
foreach ($this->graph[$pathcandidates[0][1]] as $edge) {
if ($edge->to == $pathcandidates[0][0]) {
$foundedge = $edge;
break;
}
}
if ($foundedge->space <= $teamvote[$toteamid[$pathcandidates[0][2]]]) {
$constraintflag = false;
}

if ($constraintflag) {
// We just found the shortest path fulfilling the constraint.
return $pathcandidates[0];
}
$constraintflag = true;

// Array of the potential next shortest paths.
$nextpaths = array();
$restoreedges = array();
$restorenodes = array();

// Now find the next shortest path.
$k = 1;
// Exit if there are no more shortest paths (nopathfound=true).
while (!$nopathfound && $k < 100) {
for ($i = 0; $i < count($pathcandidates[$k - 1]); $i++) {

var_dump("Im algo </br>");
var_dump($pathcandidates);
var_dump(":Pathcandidates </br>");

// Spurnode ranges from first to next to last node in previous shortest path.
$spurnode = $pathcandidates[$k - 1][$i];
$rootpath = array_slice($pathcandidates[$k - 1], 0, $i+1, true);

foreach ($pathcandidates as $path) {

if ($rootpath == array_slice($path, 0, $i+1, true)) {
foreach ($this->graph[$path[$i + 1]] as $index => $edge) {
if ($edge->to == $path[$i]) {
// Remove the links that are part of the previous shortest paths.
// Which share the same root path.
$restoreedges[$path[$i + 1]][$index] = $edge;
array_splice($this->graph[$path[$i + 1]], $index, 1);
break;
}
}
} else {
continue;
}

foreach ($rootpath as $rootpathnode) {
if ($rootpathnode != $spurnode) {
// Remove $rootpathnode from graph.
foreach ($this->graph as $index => $graphnode) {
if ($graphnode == $rootpathnode) {
$restorenodes[$index] = $graphnode;
unset($this->graph[$index]);
}
}
}
}

// Calculate the spur path from the spur node to the sink.
$spurpath = $this->find_shortest_path_bellf($i, $to);

// Entire path is made up of the root path and spur path.
$totalpath = array_merge($rootpath, $spurpath);

// Add the potential next shortest path to the heap.
$nextpaths[] = $totalpath;

// Now add back edges and nodes that were removed from the graph.
foreach ($restoreedges as $index1 => $node) {
foreach ($node as $index2 => $edge) {
$this->graph[$index1][$index2] = $edge;
}
}
foreach ($restorenodes as $index => $node) {
$this->graph[$index] = $node;
}
}

if (empty($nextpaths)) {
var_dump("No path found </br>");
$nopathfound = true;
break;
}

var_dump($nextpaths);
// Sort the potential next shortest paths by cost. -> nextpaths[0] = best path with lowest cost.
usort($nextpaths, function ($path1, $path2) {
return ($this->get_cost_of_path($path1) - $this->get_cost_of_path($path2));
});
var_dump("</br> Sortieren... </br>");
var_dump($nextpaths);

// Check if the next best path fullfillst our constraint.
foreach ($this->graph[$nextpaths[0][1]] as $edge) {
if ($edge->to == $nextpaths[0][0]) {
$foundedge = $edge;
break;
}
}
if ($foundedge->space <= $teamvote[$toteamid[$nextpaths[0][2]]]) {
$constraintflag = false;
}

if ($constraintflag) {
var_dump("Path found");
return $nextpaths[0];
}

$pathcandidates[$k] = $nextpaths[0];

// Reset flag condition.
$constraintflag = true;

array_pop($nextpaths);
}
$k++;
}
return null;
}

/**
* Returns the cost of the path by adding the weight of all edges in the path.
*
* @param $path
* @return int cost
*/
private function get_cost_of_path ($path) {

$cost = 0;

for ($i = count($path) - 1; $i > 0; $i--) {
$from = $path[$i];
$to = $path[$i - 1];
$edge = null;
// Find the edge.
foreach ($this->graph[$from] as $index => $edge) {
if ($edge->to == $to) {
$cost += $edge->weight;
break;
}
}
}

return $cost;
}

/**
* Bellman-Ford acc. to Cormen
*
* @param $from index of starting node
* @param $to index of end node
* @param $from int index of starting node
* @param $to int index of end node
* @return array with the of the nodes in the path
*/
private function find_shortest_path_bellf($from, $to) {

// We have to alter this method to fit teamvote (find the shortest path with flow >= teammembers).
// This is a constrained shortest path first problem.

// Table of distances known so far.
$dists = array();
// Table of predecessors (used to reconstruct the shortest path later).
Expand Down
2 changes: 1 addition & 1 deletion solver/solver-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public function distribute_users(\ratingallocate $ratingallocate) {
$userids = array();
foreach ($distributions as $choiceid => $teamids) {
foreach ($teamids as $teamid) {
$userids[$teamid] = groups_get_members($teamid, 'id');
$userids[$teamid] = groups_get_members($teamid, 'u.id');
}
$userdistributions[$choiceid] = array_merge($userids);
}
Expand Down

0 comments on commit 9027fb8

Please sign in to comment.