Skip to content

Commit

Permalink
modify distribution algorithm, save groupid to db for each rating and…
Browse files Browse the repository at this point in the history
… add field preventvotenotingroup to ratingallocate table
  • Loading branch information
irinahpe committed Mar 4, 2024
1 parent 53b8ad2 commit 3814171
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 60 deletions.
1 change: 1 addition & 0 deletions db/install.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
<FIELD NAME="algorithmstatus" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="-1 failure while running algorithm; 0 algorithm has not been running; 1 algorithm running; 2 algorithm finished"/>
<FIELD NAME="teamvote" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Do students vote in teams?"/>
<FIELD NAME="teamvotegroupingid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="A grouping id to get groups for team votings"/>
<FIELD NAME="preventvotenotingroup" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="If enabled a user will be unable to give a rating unless they are a member of a group in the given grouping"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
Expand Down
8 changes: 6 additions & 2 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,13 @@ function xmldb_ratingallocate_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2023050900, 'ratingallocate');
}

if ($oldversion < 2024020500) {
if ($oldversion < 2024030100) {

// Define fields teamvote and teamvotegroupingid to be added to ratingallocate.
$table = new xmldb_table('ratingallocate');
$field_teamvote = new xmldb_field('teamvote', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$field_teamvotegroupingid = new xmldb_field('teamvotegroupingid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$field_preventvotenotingroup = new xmldb_field('preventvotenotingroup', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');

// Conditionally launch add fields to ratingallocate table.
if (!$dbman->field_exists($table, $field_teamvote)) {
Expand All @@ -232,6 +233,9 @@ function xmldb_ratingallocate_upgrade($oldversion) {
if (!$dbman->field_exists($table, $field_teamvotegroupingid)) {
$dbman->add_field($table, $field_teamvotegroupingid);
}
if (!$dbman->field_exists($table, $field_preventvotenotingroup)) {
$dbman->add_field($table, $field_preventvotenotingroup);
}

// Define field groupid to be added to ratingallocate_ratings.
$ratingstable = new xmldb_table('ratingallocate_ratings');
Expand All @@ -243,7 +247,7 @@ function xmldb_ratingallocate_upgrade($oldversion) {
}

// Ratingallocate savepoint reached.
upgrade_mod_savepoint(true, 2024020500, 'ratingallocate');
upgrade_mod_savepoint(true, 2024030100, 'ratingallocate');
}

return true;
Expand Down
9 changes: 9 additions & 0 deletions lang/en/ratingallocate.php
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,15 @@
$string['strategyspecificoptions'] = 'Strategy specific options';
$string['strategy_altered_after_preferences'] = 'Strategy cannot be changed after preferences where submitted';

$string['groupvotesettings'] = 'Group Voting Settings';
$string['teamvote'] = 'Students rate in groups';
$string['teamvote_help'] = 'If enabled, students will be divided into groups based on the default set of groups or a custom grouping. A group rating will count for all group members.';
$string['teamvotegroupingid'] = 'Grouping for student groups';
$string['teamvotegroupingid_help'] = 'This is the grouping that the assignment will use to find groups for student groups. If not set, the default set of groups will be used.';
$string['teamvote_altered_after_preferences'] = 'Group Voting settings cannot be changed after preferences were submitted';
$string['preventvotenotingroup'] = 'Require group to vote';
$string['preventvotenotingroup_help'] = 'If enabled, users who are not members of a group will be unable to give a rating.';

$string['err_required'] = 'You need to provide a value for this field.';
$string['err_minimum'] = 'The minimum value for this field is {$a}.';
$string['err_maximum'] = 'The maximum value for this field is {$a}.';
Expand Down
178 changes: 155 additions & 23 deletions locallib.php
Original file line number Diff line number Diff line change
Expand Up @@ -1286,24 +1286,81 @@ public function get_ratings_for_rateable_choices() {
public function get_teamvote_goups() {
if ($this->db->get_field(this_db\ratingallocate::TABLE, 'teamvote', ['id' => $this->ratingallocateid]) == 1) {

$groupingid = $this->db->get_field(this_db\ratingallocate::TABLE, 'teamvotegroupingid', ['id' => $this->ratingallocateid]);

// 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));

$groupdata = new stdClass();
$groupdata->courseid =$this->course->id;
$groupdata->idnumber = $this->ratingallocateid;
$groupdata->name = 'delete after algorithm run';

foreach ($usersnogroup as $user) {

// Create group and add user. Group will be deleted after distributing the users
$groupid = groups_create_group($groupdata);
groups_add_member($groupid, $user);

// Add group to grouping.
$this->db->insert_record('groupings_groups', ['groupingid' => $groupingid, 'groupid' => $groupid]);

// Add groupid to ratings of this user.
$this->add_groupid_to_ratings($user->id, $groupid);

}
}

// Get the groups that are in the teamvote grouping and their amount of groupmembers.
$sql = 'SELECT m.groupid as groupid, COUNT(m.userid) AS members
FROM {groupings_groups} g INNER JOIN {groups_members} m ON g.groupid=m.groupid
WHERE g.groupingid = :groupingid
GROUP BY groupid';
$groupingid = $this->db->get_field(this_db\ratingallocate::TABLE, 'teamvotegroupingid', ['id' => $this->ratingallocateid]);


// Return array should have the form groupid => membercount.
$groups = array_map(function ($record) {
return $record->members;
}, $this->db->get_records_sql($sql, ['groupingid' => $groupingid]));
return $groups;
}
// Anderen Default return überlegen? passend zu Graphenkanten.

return false;
}

public function get_users_in_teams() {
/**
* Adds the groupid to all rating records with this userid. Should only be used for ratings with groupid 0.
*
* @param $userid
* @param $groupid
* @return void
* @throws dml_exception
*/
public function add_groupid_to_ratings($userid, $groupid) {

$sql = 'SELECT ra.* FROM {ratingallocate_ratings} ra INNER JOIN {ratingallocate_choices} c
ON ra.choiceid=c.id WHERE c.ratingallocateid = :ratingallocateid AND ra.userid = :userid';
$ratings = $this->db->get_records_sql($sql, ['ratingallocateid' => $this->ratingallocateid, 'userid' => $userid]);
foreach ($ratings as $rating) {
$rating->groupid = $groupid;
$this->db->update_record('ratingallocate_ratings', $rating);
}

}

public function delete_groups_for_usersnogroup($usergroups) {

$sql = 'SELECT id FROM {groups} WHERE id IN ( :groups ) AND idnumber = :ratingallocateid AND name = :name';
$delgroups = $this->db->get_records_sql($sql, [
'groups' => implode(" , ", array_keys($usergroups)),
'ratingallocateid' => $this->ratingallocateid,
'name' => 'delete after algorithm run'
]);
foreach ($delgroups as $group) {
groups_delete_group($group);
}

}

Expand Down Expand Up @@ -1635,17 +1692,20 @@ public function create_moodle_groups() {
* @return array
*/
public function get_rating_data_for_user($userid) {

$sql = "SELECT c.id as choiceid, c.title, c.explanation, c.ratingallocateid,
c.maxsize, c.usegroups, r.rating, r.id AS ratingid, r.userid
FROM {ratingallocate_choices} c
LEFT JOIN {ratingallocate_ratings} r
ON c.id = r.choiceid and r.userid = :userid
WHERE c.ratingallocateid = :ratingallocateid AND c.active = 1
ORDER by c.title";
c.maxsize, c.usegroups, r.rating, r.id AS ratingid, r.userid
FROM {ratingallocate_choices} c
LEFT JOIN {ratingallocate_ratings} r
ON c.id = r.choiceid and r.userid = :userid
WHERE c.ratingallocateid = :ratingallocateid AND c.active = 1
ORDER by c.title";

return $this->db->get_records_sql($sql, array(
'ratingallocateid' => $this->ratingallocateid,
'userid' => $userid
'ratingallocateid' => $this->ratingallocateid,
'userid' => $userid
));

}

/**
Expand Down Expand Up @@ -1698,7 +1758,7 @@ public function delete_all_ratings() {
}

/**
* Delete all ratings of a users
* Delete all ratings of a users and if teamvote is enabled also the ratings of all groupmembers
* @param int $userid
*/
public function delete_ratings_of_user($userid) {
Expand All @@ -1710,14 +1770,33 @@ public function delete_ratings_of_user($userid) {

$choices = $this->get_choices();

foreach ($choices as $id => $choice) {
$data = array(
$teamvote = ($DB->get_field('ratingallocate', 'teamvote', ['id' => $this->ratingallocateid]) == 1);
if ($teamvote && $votegroup=$this->get_vote_group($userid)) {

// If teamvote is enabled, delete ratings for this group.
foreach ($choices as $id => $choice) {
$data = array(
'groupid' => $votegroup->id,
'choiceid' => $id
);

// Actually delete the rating.
$DB->delete_records('ratingallocate_ratings', $data);
}

} else {

// Delete rating for just this user.
foreach ($choices as $id => $choice) {
$data = array(
'userid' => $userid,
'choiceid' => $id
);
);

// Actually delete the rating.
$DB->delete_records('ratingallocate_ratings', $data);
}

// Actually delete the rating.
$DB->delete_records('ratingallocate_ratings', $data);
}

$transaction->allow_commit();
Expand All @@ -1741,23 +1820,44 @@ public function save_ratings_to_db($userid, array $data) {
$transaction = $DB->start_delegated_transaction();
$loggingdata = array();
try {

$teamvote = ($DB->get_field('ratingallocate', 'teamvote', ['id' => $this->ratingallocateid]) == 1);
if ($teamvote && $votegroup=$this->get_vote_group($userid)) {
$votegroupid = $votegroup->id;
$ratingexists = array(
'groupid' => $votegroupid
);
} else {
$votegroupid = 0;
$ratingexists = array(
'userid' => $userid
);
}

foreach ($data as $id => $rdata) {
$rating = new stdClass ();
$rating->rating = $rdata['rating'];

$ratingexists = array(
'choiceid' => $rdata['choiceid'],
'userid' => $userid
);
$ratingexists['choiceid'] = $rdata['choiceid'];
if ($DB->record_exists('ratingallocate_ratings', $ratingexists)) {
// The rating exists, we need to update its value
// We get the id from the database.
// We get the id from the database. (There are records for each userid so ignore multiple).

$oldrating = $DB->get_record('ratingallocate_ratings', $ratingexists);
$oldrating = $DB->get_record('ratingallocate_ratings', $ratingexists, IGNORE_MULTIPLE);
if ($oldrating->{this_db\ratingallocate_ratings::RATING} != $rating->rating) {
$rating->id = $oldrating->id;
$rating->groupid = $votegroupid;
$DB->update_record('ratingallocate_ratings', $rating);

// If teamvote is enabled, update the ratings for all groupmembers.
if ($teamvote && $votegroup) {
$teammembers = groups_get_members($votegroupid, 'u.id');
foreach ($teammembers as $member) {
$rating->userid = $member->id;
$DB->update_record('ratingallocate_ratings', $rating);
}
}

// Logging.
array_push($loggingdata,
array('choiceid' => $oldrating->choiceid, 'rating' => $rating->rating));
Expand All @@ -1768,8 +1868,18 @@ public function save_ratings_to_db($userid, array $data) {
$rating->userid = $userid;
$rating->choiceid = $rdata['choiceid'];
$rating->ratingallocateid = $this->ratingallocateid;
$rating->groupid = $votegroupid;
$DB->insert_record('ratingallocate_ratings', $rating);

// If teamvote is enabled, create ratings for all groupmembers.
if ($teamvote && $votegroup) {
$teammembers = groups_get_members($votegroupid, 'u.id');
foreach ($teammembers as $member) {
$rating->userid = $member->id;
$DB->insert_record('ratingallocate_ratings', $rating);
}
}

// Logging.
array_push($loggingdata,
array('choiceid' => $rating->choiceid, 'rating' => $rating->rating));
Expand All @@ -1789,6 +1899,28 @@ public function save_ratings_to_db($userid, array $data) {
}
}

/**
* This is used for team votings to get the group for the specified user.
* If the user is a member of multiple or no groups this will return false
*
* @param int $userid The id of the user whose rating we want
* @return mixed The group or false
*/
public function get_vote_group($userid) {

global $DB;

$teamgroupingid = $DB->get_field('ratingallocate', 'teamvotegroupingid', ['id' => $this->ratingallocateid]);
$usergroups = groups_get_all_groups($this->course->id, $userid, $teamgroupingid, 'g.*', false, true);
if (count($usergroups) != 1) {
$return = false;
} else {
$return = array_pop($usergroups);
}

return $return;
}

/**
* Returns all active choices in the instance with $ratingallocateid
*/
Expand Down
56 changes: 42 additions & 14 deletions solver/edmonds-karp.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,60 @@ public function get_name() {
return 'edmonds_karp';
}

public function compute_distribution($choicerecords, $ratings, $usercount) {
public function compute_distribution($choicerecords, $ratings, $usercount, $teamvote) {
$choicedata = array();
foreach ($choicerecords as $record) {
$choicedata[$record->id] = $record;
}

$choicecount = count($choicedata);

// Index of source and sink in the graph.
$source = 0;
$sink = $choicecount + $usercount + 1;

list($fromuserid, $touserid, $fromchoiceid, $tochoiceid) = $this->setup_id_conversions($usercount, $ratings);
if (!$teamvote) {

$sink = $choicecount + $usercount + 1;

list($fromuserid, $touserid, $fromchoiceid, $tochoiceid) = $this->setup_id_conversions($usercount, $ratings);

$this->setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, -1);

// Now that the datastructure is complete, we can start the algorithm
// This is an adaptation of the Ford-Fulkerson algorithm
// 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.
// Reverse the augmentin path, thereby distributing a user into a group.
$this->augment_flow($path);
unset($path); // Clear up old path.
}
return $this->extract_allocation($touserid, $tochoiceid);

} else {

$teamcount = count($teamvote);
$sink = $choicecount + $teamcount + 1;

list($fromteamid, $toteamid, $fromchoiceid, $tochoiceid) = $this->setup_id_conversions_for_teamvote($teamcount, $ratings);

$this->setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, -1);
$this->setup_graph_for_teamvote($choicecount, $teamcount, $fromteamid, $fromchoiceid, $ratings, $choicedata, $source, $sink, -1);

// Now that the datastructure is complete, we can start the algorithm
// This is an adaptation of the Ford-Fulkerson algorithm
// 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.
// Reverse the augmentin path, thereby distributing a user into a group.
$this->augment_flow($path);
unset($path); // Clear up old path.
}
return $this->extract_allocation($toteamid, $tochoiceid);

// Now that the datastructure is complete, we can start the algorithm
// This is an adaptation of the Ford-Fulkerson algorithm
// 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.
// Reverse the augmentin path, thereby distributing a user into a group.
$this->augment_flow($path);
unset($path); // Clear up old path.
}
return $this->extract_allocation($touserid, $tochoiceid);

}

/**
Expand Down
Loading

0 comments on commit 3814171

Please sign in to comment.