diff --git a/.travis.yml b/.travis.yml
index c1a676c1..0a7b1335 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -28,9 +28,11 @@ env:
matrix:
- DB=pgsql MOODLE_BRANCH=MOODLE_34_STABLE
- DB=pgsql MOODLE_BRANCH=MOODLE_35_STABLE
+ - DB=pgsql MOODLE_BRANCH=MOODLE_36_STABLE
- DB=pgsql MOODLE_BRANCH=master
- DB=mysqli MOODLE_BRANCH=MOODLE_34_STABLE
- DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE
+ - DB=mysqli MOODLE_BRANCH=MOODLE_36_STABLE
- DB=mysqli MOODLE_BRANCH=master
before_install:
@@ -51,7 +53,7 @@ jobs:
# packages:
# - oracle-java8-installer
# - oracle-java8-set-default
- env: DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE
+ env: DB=mysqli MOODLE_BRANCH=MOODLE_36_STABLE
install:
- moodle-plugin-ci install --no-init
script:
@@ -67,7 +69,7 @@ jobs:
# Smaller build matrix for development builds
- stage: develop
php: 7.2
- env: DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE
+ env: DB=mysqli MOODLE_BRANCH=MOODLE_36_STABLE
install:
- moodle-plugin-ci install
script:
diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php
new file mode 100644
index 00000000..9152adc2
--- /dev/null
+++ b/algorithm/edmondskarp/classes/algorithm_impl.php
@@ -0,0 +1,299 @@
+.
+
+/**
+ *
+ * Contains the algorithm for the distribution
+ *
+ * @package raalgo_edmondskarp
+ * @copyright 2014 M Schulze
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace raalgo_edmondskarp;
+defined('MOODLE_INTERNAL') || die();
+
+class algorithm_impl extends \mod_ratingallocate\algorithm {
+
+ /** @var $graph Flow-Graph built */
+ protected $graph;
+
+ public function get_name() {
+ return 'edmonds_karp';
+ }
+
+ public function get_subplugin_name() {
+ return 'edmondskarp';
+ }
+
+ public function compute_distribution($choicerecords, $ratings, $usercount) {
+ $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);
+
+ $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);
+ }
+
+ /**
+ * Bellman-Ford acc. to Cormen
+ *
+ * @param $from index of starting node
+ * @param $to index of end node
+ * @return array with the of the nodes in the path
+ */
+ private function find_shortest_path_bellf($from, $to) {
+ // Table of distances known so far.
+ $dists = array();
+ // Table of predecessors (used to reconstruct the shortest path later).
+ $preds = array();
+
+ // Number of nodes in the graph.
+ $count = $this->graph['count'];
+
+ // Step 1: initialize graph.
+ for ($i = 0; $i < $count; $i++) { // For each vertex v in vertices:
+ if ($i == $from) {// If v is source then weight[v] := 0.
+ $dists[$i] = 0;
+ } else {// Else weight[v] := infinity.
+ $dists[$i] = INF;
+ }
+ $preds[$i] = null; // Predecessor[v] := null.
+ }
+
+ // Step 2: relax edges repeatedly.
+ for ($i = 0; $i < $count; $i++) { // For i from 1 to size(vertices)-1.
+ $updatedsomething = false;
+ foreach ($this->graph as $key => $edges) { // For each edge (u, v) with weight w in edges.
+ if (is_array($edges)) {
+ foreach ($edges as $key2 => $edge) {
+ /* @var $edge edge */
+ if ($dists[$edge->from] + $edge->weight < $dists[$edge->to]) { // If weight[u] + w < weight[v].
+ $dists[$edge->to] = $dists[$edge->from] + $edge->weight; // Weight[v] := weight[u] + w.
+ $preds[$edge->to] = $edge->from; // Predecessor[v] := u.
+ $updatedsomething = true;
+ }
+ }
+ }
+ }
+ if (!$updatedsomething) {
+ break; // Leave.
+ }
+ }
+
+ // Step 3: check for negative-weight cycles.
+ /* Foreach ($graph as $key => $edges) { // for each edge (u, v) with weight w in edges:
+ if (is_array($edges)) {
+ foreach ($edges as $key2 => $edge) {
+
+ if ($dists[$edge->to] + $edge->weight < $dists[$edge->to]) { // if weight[u] + w < weight[v]:
+ print_error('negative_cycle', 'ratingallocate');
+ }
+ }
+ }
+ }*/
+
+ // If there is no path to $to, return null.
+ if (is_null($preds[$to])) {
+ return null;
+ }
+
+ // Cleanup dists to save some space.
+ unset($dists);
+
+ // Use the preds table to reconstruct the shortest path.
+ $path = array();
+ $p = $to;
+ while ($p != $from) {
+ $path[] = $p;
+ $p = $preds[$p];
+ }
+ $path[] = $from;
+ return $path;
+ }
+
+ /**
+ * Extracts a distribution/allocation from the graph.
+ *
+ * @param $touserid a map mapping from indexes in the graph to userids
+ * @param $tochoiceid a map mapping from indexes in the graph to choiceids
+ * @return array of the form array(groupid => array(userid, ...), ...)
+ */
+ protected function extract_allocation($touserid, $tochoiceid) {
+ $distribution = array();
+ foreach ($tochoiceid as $index => $groupid) {
+ $group = $this->graph[$index];
+ $distribution[$groupid] = array();
+ foreach ($group as $assignment) {
+ /* @var $assignment edge */
+ $user = intval($assignment->to);
+ if (array_key_exists($user, $touserid)) {
+ $distribution[$groupid][] = $touserid[$user];
+ }
+ }
+ }
+ return $distribution;
+ }
+
+ /**
+ * Setup conversions between ids of users and choices to their node-ids in the graph
+ * @param $usercount
+ * @param $ratings
+ * @return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid);
+ */
+ public static function setup_id_conversions($usercount, $ratings) {
+ // These tables convert userids to their index in the graph.
+ // The range is [1..$usercount].
+ $fromuserid = array();
+ $touserid = array();
+ // These tables convert choiceids to their index in the graph.
+ // The range is [$usercount + 1 .. $usercount + $choicecount].
+ $fromchoiceid = array();
+ $tochoiceid = array();
+
+ // User counter.
+ $ui = 1;
+ // Group counter.
+ $gi = $usercount + 1;
+
+ // Fill the conversion tables for group and user ids.
+ foreach ($ratings as $rating) {
+ if (!array_key_exists($rating->userid, $fromuserid)) {
+ $fromuserid[$rating->userid] = $ui;
+ $touserid[$ui] = $rating->userid;
+ $ui++;
+ }
+ if (!array_key_exists($rating->choiceid, $fromchoiceid)) {
+ $fromchoiceid[$rating->choiceid] = $gi;
+ $tochoiceid[$gi] = $rating->choiceid;
+ $gi++;
+ }
+ }
+
+ return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid);
+ }
+
+ /**
+ * Sets up $this->graph
+ * @param $choicecount
+ * @param $usercount
+ * @param $fromuserid
+ * @param $fromchoiceid
+ * @param $ratings
+ * @param $choicedata
+ * @param $source
+ * @param $sink
+ */
+ protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source,
+ $sink, $weightmult = 1) {
+ // Construct the datastructures for the algorithm.
+ // A directed weighted bipartite graph.
+ // A source is connected to all users with unit cost.
+ // The users are connected to their choices with cost equal to their rating.
+ // The choices are connected to a sink with 0 cost.
+ $this->graph = array();
+ // Add source, sink and number of nodes to the graph.
+ $this->graph[$source] = array();
+ $this->graph[$sink] = array();
+ $this->graph['count'] = $choicecount + $usercount + 2;
+
+ // Add users and choices to the graph and connect them to the source and sink.
+ foreach ($fromuserid as $id => $user) {
+ $this->graph[$user] = array();
+ $this->graph[$source][] = new edge($source, $user, 0);
+ }
+ foreach ($fromchoiceid as $id => $choice) {
+ $this->graph[$choice] = array();
+ $this->graph[$choice][] = new edge($choice, $sink, 0, $choicedata[$id]->maxsize);
+ }
+
+ // Add the edges representing the ratings to the graph.
+ foreach ($ratings as $id => $rating) {
+ $user = $fromuserid[$rating->userid];
+ $choice = $fromchoiceid[$rating->choiceid];
+ $weight = $rating->rating;
+ if ($weight > 0) {
+ $this->graph[$user][] = new edge($user, $choice, $weightmult * $weight);
+ }
+ }
+ }
+
+ /**
+ * Augments the flow in the network, i.e. augments the overall 'satisfaction'
+ * by distributing users to choices
+ * Reverses all edges along $path in $graph
+ * @param $path path from t to s
+ */
+ protected function augment_flow($path) {
+ if (is_null($path) or count($path) < 2) {
+ print_error('invalid_path', 'ratingallocate');
+ }
+
+ // Walk along the path, from s to t.
+ for ($i = count($path) - 1; $i > 0; $i--) {
+ $from = $path[$i];
+ $to = $path[$i - 1];
+ $edge = null;
+ $foundedgeid = -1;
+ // Find the edge.
+ foreach ($this->graph[$from] as $index => &$edge) {
+ /* @var $edge edge */
+ if ($edge->to == $to) {
+ $foundedgeid = $index;
+ break;
+ }
+ }
+ // The second to last node in a path has to be a choice-node.
+ // Reduce its space by one, because one user just got distributed into it.
+ if ($i == 1 and $edge->space > 1) {
+ $edge->space --;
+ } else {
+ // Remove the edge.
+ array_splice($this->graph[$from], $foundedgeid, 1);
+ // Add a new edge in the opposite direction whose weight has an opposite sign.
+ // Array_push($this->graph[$to], new edge($to, $from, -1 * $edge->weight));
+ // According to php doc, this is faster.
+ $this->graph[$to][] = new edge($to, $from, -1 * $edge->weight);
+ }
+ }
+ }
+
+ /**
+ * Supports neither min size nor optional.
+ * @return bool[]
+ */
+ public static function get_supported_features() {
+ return ['min' => false, 'opt' => false];
+ }
+}
diff --git a/algorithm/edmondskarp/classes/edge.php b/algorithm/edmondskarp/classes/edge.php
new file mode 100644
index 00000000..f2ebe23b
--- /dev/null
+++ b/algorithm/edmondskarp/classes/edge.php
@@ -0,0 +1,49 @@
+.
+
+/**
+ *
+ * Contains the algorithm for the distribution
+ *
+ * @package raalgo_edmondskarp
+ * @copyright 2014 M Schulze
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace raalgo_edmondskarp;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Represents an Edge in the graph to have fixed fields instead of array-fields
+ */
+class edge {
+ /** @var from int */
+ public $from;
+ /** @var to int */
+ public $to;
+ /** @var weight int Cost for this edge (rating of user) */
+ public $weight;
+ /** @var space int (places left for choices) */
+ public $space;
+
+ public function __construct($from, $to, $weight, $space = 0) {
+ $this->from = $from;
+ $this->to = $to;
+ $this->weight = $weight;
+ $this->space = $space;
+ }
+
+}
\ No newline at end of file
diff --git a/algorithm/edmondskarp/lang/en/raalgo_edmondskarp.php b/algorithm/edmondskarp/lang/en/raalgo_edmondskarp.php
new file mode 100644
index 00000000..6f5690c2
--- /dev/null
+++ b/algorithm/edmondskarp/lang/en/raalgo_edmondskarp.php
@@ -0,0 +1,25 @@
+.
+
+
+/**
+ * English strings for raalgo_edmondskarp
+ *
+ * @package raalgo_edmondskarp
+ * @copyright 2019 J Dageförde, N Herrmann
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+$string['pluginname'] = 'Edmonds-Karp algorithm';
\ No newline at end of file
diff --git a/algorithm/edmondskarp/version.php b/algorithm/edmondskarp/version.php
new file mode 100644
index 00000000..100d7572
--- /dev/null
+++ b/algorithm/edmondskarp/version.php
@@ -0,0 +1,30 @@
+.
+
+
+/**
+ * Defines the version of ratingallocate
+ *
+ * @package raalgo_edmondskarp
+ * @copyright 2019 J Dageförde, N Herrmann
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX)
+$plugin->requires = 2017111300; // Requires this Moodle version
+$plugin->component = 'raalgo_edmondskarp'; // To check on upgrade, that module sits in correct place.
diff --git a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php
new file mode 100644
index 00000000..df5518cd
--- /dev/null
+++ b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php
@@ -0,0 +1,313 @@
+.
+
+/**
+ *
+ * Contains the algorithm for the distribution
+ *
+ * @package raalgo_fordfulkersonkoegel
+ * @copyright 2014 M Schulze
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace raalgo_fordfulkersonkoegel;
+defined('MOODLE_INTERNAL') || die();
+
+class algorithm_impl extends \mod_ratingallocate\algorithm {
+
+ /** @var $graph Flow-Graph built */
+ protected $graph;
+
+ public function get_subplugin_name() {
+ return 'fordfulkersonkoegel';
+ }
+
+ /**
+ * Starts the distribution algorithm.
+ * Uses the users' ratings and a minimum-cost maximum-flow algorithm
+ * to distribute the users fairly into the groups.
+ * (see http://en.wikipedia.org/wiki/Minimum-cost_flow_problem)
+ * After the algorithm is done, users are removed from their current
+ * groups (see clear_all_groups_in_course()) and redistributed
+ * according to the computed distriution.
+ *
+ */
+ public function compute_distribution($choicerecords, $ratings, $usercount) {
+ $groupdata = array();
+ foreach ($choicerecords as $record) {
+ $groupdata[$record->id] = $record;
+ }
+
+ $groupcount = count($groupdata);
+ // Index of source and sink in the graph.
+ $source = 0;
+ $sink = $groupcount + $usercount + 1;
+ list($fromuserid, $touserid, $fromgroupid, $togroupid) = $this->setup_id_conversions($usercount, $ratings);
+
+ $this->setup_graph($groupcount, $usercount, $fromuserid, $fromgroupid, $ratings, $groupdata, $source, $sink);
+
+ // Now that the datastructure is complete, we can start the algorithm.
+ // This is an adaptation of the Ford-Fulkerson algorithm.
+ // (http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm).
+ for ($i = 1; $i <= $usercount; $i++) {
+ // Look for an augmenting path (a shortest path from the source to the sink).
+ $path = $this->find_shortest_path_bellmanf_koegel($source, $sink);
+ // If there is no such path, it is impossible to fit any more users into groups.
+ if (is_null($path)) {
+ // Stop the algorithm.
+ continue;
+ }
+ // Reverse the augmenting path, thereby distributing a user into a group.
+ $this->augment_flow($path);
+ }
+
+ return $this->extract_allocation($touserid, $togroupid);
+ }
+
+ /**
+ * Uses a modified Bellman-Ford algorithm to find a shortest path
+ * from $from to $to in $graph. We can't use Dijkstra here, because
+ * the graph contains edges with negative weight.
+ *
+ * @param $from index of starting node
+ * @param $to index of end node
+ * @return array with the of the nodes in the path
+ */
+ public function find_shortest_path_bellmanf_koegel($from, $to) {
+
+ // Table of distances known so far.
+ $dists = array();
+ // Table of predecessors (used to reconstruct the shortest path later).
+ $preds = array();
+ // Stack of the edges we need to test next.
+ $edges = $this->graph[$from];
+ // Number of nodes in the graph.
+ $count = $this->graph['count'];
+
+ // To prevent the algorithm from getting stuck in a loop with
+ // with negative weight, we stop it after $count ^ 3 iterations.
+ $counter = 0;
+ $limit = $count * $count * $count;
+
+ // Initialize dists and preds.
+ for ($i = 0; $i < $count; $i++) {
+ if ($i == $from) {
+ $dists[$i] = 0;
+ } else {
+ $dists[$i] = -INF;
+ }
+ $preds[$i] = null;
+ }
+
+ while (!empty($edges) and $counter < $limit) {
+ $counter++;
+
+ /* @var e edge */
+ $e = array_pop($edges);
+
+ $f = $e->from;
+ $t = $e->to;
+ $dist = $e->weight + $dists[$f];
+
+ // If this edge improves a distance update the tables and the edges stack.
+ if ($dist > $dists[$t]) {
+ $dists[$t] = $dist;
+ $preds[$t] = $f;
+ foreach ($this->graph[$t] as $newedge) {
+ $edges[] = $newedge;
+ }
+ }
+ }
+
+ // A valid groupdistribution graph can't contain a negative edge.
+ if ($counter == $limit) {
+ print_error('negative_cycle', 'ratingallocate');
+ }
+
+ // If there is no path to $to, return null.
+ if (is_null($preds[$to])) {
+ return null;
+ }
+
+ // Use the preds table to reconstruct the shortest path.
+ $path = array();
+ $p = $to;
+ while ($p != $from) {
+ $path[] = $p;
+ $p = $preds[$p];
+ }
+ $path[] = $from;
+
+ return $path;
+ }
+
+ public function get_name() {
+ return "ford-fulkerson Koegel2014";
+ }
+
+ /**
+ * Extracts a distribution/allocation from the graph.
+ *
+ * @param $touserid a map mapping from indexes in the graph to userids
+ * @param $tochoiceid a map mapping from indexes in the graph to choiceids
+ * @return array of the form array(groupid => array(userid, ...), ...)
+ */
+ protected function extract_allocation($touserid, $tochoiceid) {
+ $distribution = array();
+ foreach ($tochoiceid as $index => $groupid) {
+ $group = $this->graph[$index];
+ $distribution[$groupid] = array();
+ foreach ($group as $assignment) {
+ /* @var $assignment edge */
+ $user = intval($assignment->to);
+ if (array_key_exists($user, $touserid)) {
+ $distribution[$groupid][] = $touserid[$user];
+ }
+ }
+ }
+ return $distribution;
+ }
+
+ /**
+ * Setup conversions between ids of users and choices to their node-ids in the graph
+ * @param $usercount
+ * @param $ratings
+ * @return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid);
+ */
+ public static function setup_id_conversions($usercount, $ratings) {
+ // These tables convert userids to their index in the graph.
+ // The range is [1..$usercount].
+ $fromuserid = array();
+ $touserid = array();
+ // These tables convert choiceids to their index in the graph.
+ // The range is [$usercount + 1 .. $usercount + $choicecount].
+ $fromchoiceid = array();
+ $tochoiceid = array();
+
+ // User counter.
+ $ui = 1;
+ // Group counter.
+ $gi = $usercount + 1;
+
+ // Fill the conversion tables for group and user ids.
+ foreach ($ratings as $rating) {
+ if (!array_key_exists($rating->userid, $fromuserid)) {
+ $fromuserid[$rating->userid] = $ui;
+ $touserid[$ui] = $rating->userid;
+ $ui++;
+ }
+ if (!array_key_exists($rating->choiceid, $fromchoiceid)) {
+ $fromchoiceid[$rating->choiceid] = $gi;
+ $tochoiceid[$gi] = $rating->choiceid;
+ $gi++;
+ }
+ }
+
+ return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid);
+ }
+
+ /**
+ * Sets up $this->graph
+ * @param $choicecount
+ * @param $usercount
+ * @param $fromuserid
+ * @param $fromchoiceid
+ * @param $ratings
+ * @param $choicedata
+ * @param $source
+ * @param $sink
+ */
+ protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source,
+ $sink, $weightmult = 1) {
+ // Construct the datastructures for the algorithm.
+ // A directed weighted bipartite graph.
+ // A source is connected to all users with unit cost.
+ // The users are connected to their choices with cost equal to their rating.
+ // The choices are connected to a sink with 0 cost.
+ $this->graph = array();
+ // Add source, sink and number of nodes to the graph.
+ $this->graph[$source] = array();
+ $this->graph[$sink] = array();
+ $this->graph['count'] = $choicecount + $usercount + 2;
+
+ // Add users and choices to the graph and connect them to the source and sink.
+ foreach ($fromuserid as $id => $user) {
+ $this->graph[$user] = array();
+ $this->graph[$source][] = new edge($source, $user, 0);
+ }
+ foreach ($fromchoiceid as $id => $choice) {
+ $this->graph[$choice] = array();
+ $this->graph[$choice][] = new edge($choice, $sink, 0, $choicedata[$id]->maxsize);
+ }
+
+ // Add the edges representing the ratings to the graph.
+ foreach ($ratings as $id => $rating) {
+ $user = $fromuserid[$rating->userid];
+ $choice = $fromchoiceid[$rating->choiceid];
+ $weight = $rating->rating;
+ if ($weight > 0) {
+ $this->graph[$user][] = new edge($user, $choice, $weightmult * $weight);
+ }
+ }
+ }
+
+ /**
+ * Augments the flow in the network, i.e. augments the overall 'satisfaction'
+ * by distributing users to choices
+ * Reverses all edges along $path in $graph
+ * @param $path path from t to s
+ */
+ protected function augment_flow($path) {
+ if (is_null($path) or count($path) < 2) {
+ print_error('invalid_path', 'ratingallocate');
+ }
+
+ // Walk along the path, from s to t.
+ for ($i = count($path) - 1; $i > 0; $i--) {
+ $from = $path[$i];
+ $to = $path[$i - 1];
+ $edge = null;
+ $foundedgeid = -1;
+ // Find the edge.
+ foreach ($this->graph[$from] as $index => &$edge) {
+ /* @var $edge edge */
+ if ($edge->to == $to) {
+ $foundedgeid = $index;
+ break;
+ }
+ }
+ // The second to last node in a path has to be a choice-node.
+ // Reduce its space by one, because one user just got distributed into it.
+ if ($i == 1 and $edge->space > 1) {
+ $edge->space --;
+ } else {
+ // Remove the edge.
+ array_splice($this->graph[$from], $foundedgeid, 1);
+ // Add a new edge in the opposite direction whose weight has an opposite sign.
+ // Array_push($this->graph[$to], new edge($to, $from, -1 * $edge->weight));
+ // According to php doc, this is faster.
+ $this->graph[$to][] = new edge($to, $from, -1 * $edge->weight);
+ }
+ }
+ }
+
+ /**
+ * Supports neither min size nor optional.
+ * @return bool[]
+ */
+ public static function get_supported_features() {
+ return ['min' => false, 'opt' => false];
+ }
+}
diff --git a/algorithm/fordfulkersonkoegel/classes/edge.php b/algorithm/fordfulkersonkoegel/classes/edge.php
new file mode 100644
index 00000000..ce6c44a3
--- /dev/null
+++ b/algorithm/fordfulkersonkoegel/classes/edge.php
@@ -0,0 +1,49 @@
+.
+
+/**
+ *
+ * Contains the algorithm for the distribution
+ *
+ * @package raalgo_fordfulkersonkoegel
+ * @copyright 2014 M Schulze
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace raalgo_fordfulkersonkoegel;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Represents an Edge in the graph to have fixed fields instead of array-fields
+ */
+class edge {
+ /** @var from int */
+ public $from;
+ /** @var to int */
+ public $to;
+ /** @var weight int Cost for this edge (rating of user) */
+ public $weight;
+ /** @var space int (places left for choices) */
+ public $space;
+
+ public function __construct($from, $to, $weight, $space = 0) {
+ $this->from = $from;
+ $this->to = $to;
+ $this->weight = $weight;
+ $this->space = $space;
+ }
+
+}
\ No newline at end of file
diff --git a/algorithm/fordfulkersonkoegel/lang/en/raalgo_fordfulkersonkoegel.php b/algorithm/fordfulkersonkoegel/lang/en/raalgo_fordfulkersonkoegel.php
new file mode 100644
index 00000000..5e421b1a
--- /dev/null
+++ b/algorithm/fordfulkersonkoegel/lang/en/raalgo_fordfulkersonkoegel.php
@@ -0,0 +1,25 @@
+.
+
+
+/**
+ * English strings for raalgo_fordfulkersonkoegel
+ *
+ * @package raalgo_fordfulkersonkoegel
+ * @copyright 2019 J Dageförde, N Herrmann
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+$string['pluginname'] = 'Ford–Fulkerson-Koegel algorithm';
\ No newline at end of file
diff --git a/algorithm/fordfulkersonkoegel/version.php b/algorithm/fordfulkersonkoegel/version.php
new file mode 100644
index 00000000..8b61261c
--- /dev/null
+++ b/algorithm/fordfulkersonkoegel/version.php
@@ -0,0 +1,30 @@
+.
+
+
+/**
+ * Defines the version of raalgo_fordfulkersonkoegel
+ *
+ * @package raalgo_fordfulkersonkoegel
+ * @copyright 2019 J Dageförde, N Herrmann
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX)
+$plugin->requires = 2017111300; // Requires this Moodle version
+$plugin->component = 'raalgo_fordfulkersonkoegel'; // To check on upgrade, that module sits in correct place.
diff --git a/classes/algorithm.php b/classes/algorithm.php
new file mode 100644
index 00000000..08b437df
--- /dev/null
+++ b/classes/algorithm.php
@@ -0,0 +1,148 @@
+.
+
+namespace mod_ratingallocate;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/../locallib.php');
+
+abstract class algorithm {
+
+ /** @var \ratingallocate ratingallocate */
+ private $ratingallocate;
+
+ public function __construct($ratingallocate) {
+ $this->ratingallocate = $ratingallocate;
+ }
+
+ /**
+ * Get name of the subplugin, without the raalgo_ prefix.
+ * @return string
+ */
+ public abstract function get_subplugin_name();
+
+ /**
+ * @deprecated
+ * @return string
+ */
+ public abstract function get_name();
+
+ /**
+ * Expected return value is an array with min and opt as key and true or false as supported or not supported.
+ * @return bool[]
+ */
+ public static abstract function get_supported_features();
+ protected abstract function compute_distribution($choicerecords, $ratings, $usercount);
+
+ /**
+ * Entry-point for the \ratingallocate object to call a solver
+ * @param \ratingallocate $ratingallocate
+ */
+ public function distribute_users() {
+
+ // Load data from database.
+ $choicerecords = $this->ratingallocate->get_rateable_choices();
+ $ratings = $this->ratingallocate->get_ratings_for_rateable_choices();
+
+ // Randomize the order of the enrties to prevent advantages for early entry.
+ shuffle($ratings);
+
+ $usercount = count($this->ratingallocate->get_raters_in_course());
+
+ $distributions = $this->compute_distribution($choicerecords, $ratings, $usercount);
+
+ // Perform all allocation manipulation / inserts in one transaction.
+ $transaction = $this->ratingallocate->db->start_delegated_transaction();
+
+ $this->ratingallocate->clear_all_allocations();
+
+ foreach ($distributions as $choiceid => $users) {
+ foreach ($users as $userid) {
+ $this->ratingallocate->add_allocation($choiceid, $userid);
+ }
+ }
+ $transaction->allow_commit();
+ }
+
+ /**
+ * Compute the 'satisfaction' functions that is to be maximized by adding the
+ * ratings users gave to their allocated choices
+ * @param array $ratings
+ * @param array $distribution
+ * @return integer
+ */
+ public static function compute_target_function($ratings, $distribution) {
+ $functionvalue = 0;
+ foreach ($distribution as $choiceid => $choice) {
+ // In $choice ist jetzt ein array von userids.
+ foreach ($choice as $userid) {
+ // Jetzt das richtige Rating rausfinden.
+ foreach ($ratings as $rating) {
+ if ($rating->userid == $userid && $rating->choiceid == $choiceid) {
+ $functionvalue += $rating->rating;
+ continue; // Aus der Such-Schleife raus und weitermachen.
+ }
+ }
+
+ }
+ }
+ return $functionvalue;
+ }
+
+ /**
+ * Inserts a message to the execution_log
+ * @param string $message
+ */
+ protected function append_to_log(string $message) {
+ $log = new execution_log();
+
+ $log->set('message', $message);
+ $log->set('algorithm', $this->get_subplugin_name());
+ $log->set('ratingallocateid', $this->ratingallocate->get_id());
+ $log->save();
+
+ }
+
+ /**
+ * @param string $name Subplugin name without 'raalgo_'-prefix.
+ * @param \ratingallocate|null $ratingallocate the current ratingallocateinstance.
+ * @return algorithm Algorithm instance
+ */
+ public static function get_instance(string $name, $ratingallocate) {
+ $possible = self::get_available_algorithms();
+ if (array_key_exists($name, $possible)) {
+ $classname = '\raalgo_' . $name . '\algorithm_impl';
+ return new $classname($ratingallocate);
+ } else {
+ throw new \coding_exception('Tried to instantiate algorithm that is not installed or available.');
+ }
+ }
+
+ /**
+ * Get the list of available algorithms
+ * @return string[] of the form Name -> Displayname
+ */
+ public static function get_available_algorithms() {
+ $algorithms = \core_plugin_manager::instance()->get_plugins_of_type('raalgo');
+ $result = array();
+ foreach($algorithms as $algo) {
+ $result[$algo->name] = $algo->displayname;
+ }
+ return $result;
+ }
+
+}
\ No newline at end of file
diff --git a/classes/algorithm_testable.php b/classes/algorithm_testable.php
new file mode 100644
index 00000000..cc5086fb
--- /dev/null
+++ b/classes/algorithm_testable.php
@@ -0,0 +1,65 @@
+.
+
+namespace mod_ratingallocate;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/../locallib.php');
+
+class algorithm_testable extends algorithm {
+
+ /**
+ * Inserts a message to the execution_log
+ * @param string $message
+ */
+ public function append_to_log(string $message) {
+ parent::append_to_log($message);
+ }
+
+ /**
+ * Get name of the subplugin, without the raalgo_ prefix.
+ *
+ * @return string
+ */
+ public function get_subplugin_name() {
+ return 'algorithmtestable';
+ }
+
+ /**
+ * @deprecated
+ * @return string
+ */
+ public function get_name() {
+ return 'Algorithm Testable';
+ }
+
+ protected function compute_distribution($choicerecords, $ratings, $usercount) {
+ return null;
+ }
+
+ /**
+ * Expected return value is an array with min and opt as key and true or false as supported or not supported.
+ *
+ * @return bool[]
+ */
+ public static function get_supported_features() {
+ return array(
+ 'min' => false,
+ 'opt' => false
+ );
+ }
+}
\ No newline at end of file
diff --git a/classes/execution_log.php b/classes/execution_log.php
new file mode 100644
index 00000000..15eab423
--- /dev/null
+++ b/classes/execution_log.php
@@ -0,0 +1,63 @@
+.
+
+/**
+ * Class for loading/storing execution_log messages from the DB.
+ *
+ * @package mod_ratingallocate
+ * @copyright 2019 WWU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace mod_ratingallocate;
+
+defined('MOODLE_INTERNAL') || die();
+
+use core\persistent;
+use lang_string;
+
+/**
+ * Class for loading/storing execution_log messages from the DB.
+ *
+ * @package mod_ratingallocate
+ * @copyright 2019 WWU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class execution_log extends persistent {
+
+ const TABLE = 'ratingallocate_execution_log';
+
+ /**
+ * Return the definition of the properties of this model.
+ *
+ * @return array
+ */
+ protected static function define_properties() {
+ return array(
+ 'ratingallocateid' => array(
+ 'type' => PARAM_INT,
+ 'message' => new lang_string('error_persistent_ratingallocateid', 'mod_ratingallocate'),
+ ),
+ 'algorithm' => array(
+ 'type' => PARAM_ALPHANUM,
+ 'message' => new lang_string('error_persistent_algoname', 'mod_ratingallocate'),
+ ),
+ 'message' => array(
+ 'type' => PARAM_TEXT,
+ 'message' => new lang_string('error_persistent_message', 'mod_ratingallocate'),
+ )
+ );
+ }
+}
diff --git a/classes/plugininfo/raalgo.php b/classes/plugininfo/raalgo.php
new file mode 100644
index 00000000..dac1f198
--- /dev/null
+++ b/classes/plugininfo/raalgo.php
@@ -0,0 +1,27 @@
+.
+
+namespace mod_ratingallocate\plugininfo;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Subplugin type raalgo (allocation algorithms).
+ * @package mod_ratingallocate\plugininfo
+ */
+class raalgo extends \core\plugininfo\base {
+
+}
\ No newline at end of file
diff --git a/db/db_structure.php b/db/db_structure.php
index 2e5b30d1..dea217c1 100644
--- a/db/db_structure.php
+++ b/db/db_structure.php
@@ -49,7 +49,9 @@ class ratingallocate_choices {
const RATINGALLOCATEID = 'ratingallocateid';
const TITLE = 'title';
const EXPLANATION = 'explanation';
+ const MINSIZE = 'minsize';
const MAXSIZE = 'maxsize';
+ const OPTIONAL = 'optional';
const ACTIVE = 'active';
}
class ratingallocate_ratings {
diff --git a/db/install.xml b/db/install.xml
index b5a7b6f5..dced74b5 100644
--- a/db/install.xml
+++ b/db/install.xml
@@ -1,5 +1,5 @@
-
@@ -16,7 +16,10 @@
+
+
+
@@ -38,7 +41,9 @@
+
+
@@ -70,5 +75,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/db/subplugins.php b/db/subplugins.php
new file mode 100644
index 00000000..c63edbfb
--- /dev/null
+++ b/db/subplugins.php
@@ -0,0 +1,21 @@
+.
+
+defined('MOODLE_INTERNAL') || die();
+
+$subplugins = array(
+ 'raalgo' => 'mod/ratingallocate/algorithm',
+);
diff --git a/db/upgrade.php b/db/upgrade.php
index 14875d7f..9e4a3074 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -131,6 +131,84 @@ function xmldb_ratingallocate_upgrade($oldversion) {
}
}
-
+
+
+ if ($oldversion < 2019031803) {
+
+ // Define field minsize to be added to ratingallocate_choices.
+ $table = new xmldb_table('ratingallocate_choices');
+ $field = new xmldb_field('minsize', XMLDB_TYPE_INTEGER, '10', null, null, null, '0', 'maxsize');
+
+ // Conditionally launch add field minsize.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+ $field = new xmldb_field('optional', XMLDB_TYPE_INTEGER, '4', null, null, null, '0', 'active');
+
+ // Conditionally launch add field optional.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+ upgrade_mod_savepoint(true, 2019031803, 'ratingallocate');
+ }
+
+ if ($oldversion < 2019031901) {
+ // Define field generaloption_minsize to be added to ratingallocate.
+ $table = new xmldb_table('ratingallocate');
+ $field = new xmldb_field('generaloption_minsize', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'setting');
+
+ // Conditionally launch add field generaloption_minsize.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+ // Define field generaloption_optional to be added to ratingallocate.
+ $table = new xmldb_table('ratingallocate');
+ $field = new xmldb_field('generaloption_optional', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'generaloption_minsize');
+
+ // Conditionally launch add field generaloption_optional.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Define table ratingallocate_execution_log to be created.
+ $table = new xmldb_table('ratingallocate_execution_log');
+
+ // Adding fields to table ratingallocate_execution_log.
+ $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+ $table->add_field('ratingallocateid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('algorithm', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('message', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+ $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+ // Adding keys to table ratingallocate_execution_log.
+ $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
+ $table->add_key('ratingallocateid', XMLDB_KEY_FOREIGN, ['ratingallocateid'], 'ratingallocate', ['id']);
+
+ // Conditionally launch create table for ratingallocate_execution_log.
+ if (!$dbman->table_exists($table)) {
+ $dbman->create_table($table);
+ }
+
+ // Ratingallocate savepoint reached.
+ upgrade_mod_savepoint(true, 2019031901, 'ratingallocate');
+ }
+
+ if ($oldversion < 2019031916) {
+
+ // Define field algorithm to be added to ratingallocate.
+ $table = new xmldb_table('ratingallocate');
+ $field = new xmldb_field('algorithm', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, 'edmondskarp', 'strategy');
+
+ // Conditionally launch add field algorithm.
+ if (!$dbman->field_exists($table, $field)) {
+ $dbman->add_field($table, $field);
+ }
+
+ // Ratingallocate savepoint reached.
+ upgrade_mod_savepoint(true, 2019031916, 'ratingallocate');
+ }
+
return true;
}
diff --git a/form_modify_choice.php b/form_modify_choice.php
index b6306b28..2e693d3d 100644
--- a/form_modify_choice.php
+++ b/form_modify_choice.php
@@ -78,6 +78,15 @@ public function definition() {
$mform->addElement('text', $elementname, get_string('choice_explanation', ratingallocate_MOD_NAME));
$mform->setType($elementname, PARAM_TEXT);
+ if (true) {
+ $elementname = 'minsize';
+ $mform->addElement('text', $elementname, get_string('choice_minsize', ratingallocate_MOD_NAME));
+ $mform->setType($elementname, PARAM_TEXT);
+ $mform->addRule($elementname, get_string('err_required', 'form') , 'required', null, 'server');
+ $mform->addRule($elementname, get_string('err_numeric', 'form') , 'numeric', null, 'server');
+ $mform->addRule($elementname, get_string('err_positivnumber', 'ratingallocate') , 'regex', '/^[1-9][0-9]*|0/', 'server');
+ }
+
$elementname = 'maxsize';
$mform->addElement('text', $elementname, get_string('choice_maxsize', ratingallocate_MOD_NAME));
$mform->setType($elementname, PARAM_TEXT);
@@ -85,17 +94,28 @@ public function definition() {
$mform->addRule($elementname, get_string('err_numeric', 'form') , 'numeric', null, 'server');
$mform->addRule($elementname, get_string('err_positivnumber', 'ratingallocate') , 'regex', '/^[1-9][0-9]*|0/', 'server');
+ if (true) {
+ $mform->addRule(array("minsize", "maxsize"), get_string('err_gte', 'ratingallocate'), "compare", "lte", "server");
+ }
+
$elementname = 'active';
$mform->addElement('advcheckbox', $elementname, get_string('choice_active', ratingallocate_MOD_NAME),
null, null, array(0, 1));
$mform->addHelpButton($elementname, 'choice_active', ratingallocate_MOD_NAME);
+ $elementname = 'optional';
+ $mform->addElement('advcheckbox', $elementname, get_string('choice_optional', ratingallocate_MOD_NAME),
+ null, null, array(0, 1));
+ $mform->addHelpButton($elementname, 'choice_optional', ratingallocate_MOD_NAME);
+
if ($this->choice) {
$mform->setDefault('title', $this->choice->title);
$mform->setDefault('explanation', $this->choice->explanation);
+ $mform->setDefault('minsize', $this->choice->minsize);
$mform->setDefault('maxsize', $this->choice->maxsize);
$mform->setDefault('active', $this->choice->active);
$mform->setDefault('choiceid', $this->choice->id);
+ $mform->setDefault('optional', $this->choice->optional);
} else {
$mform->setDefault('active', true);
}
diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php
index bfef88c5..8c8e4317 100644
--- a/lang/en/ratingallocate.php
+++ b/lang/en/ratingallocate.php
@@ -193,9 +193,12 @@
//
$string['choice_active'] = 'Choice is active';
$string['choice_active_help'] = 'Only active choices are displayed to the user. Inactive choices are not displayed.';
+$string['choice_optional'] = 'Optional';
+$string['choice_optional_help'] = 'Whether the choice is allowed to be cancelled';
$string['choice_explanation'] = 'Description (optional)';
$string['choice_maxsize'] = 'Max. number of participants';
$string['choice_maxsize_display'] = 'Maximum number of students';
+$string['choice_minsize'] = 'Min. number of participants';
$string['choice_title'] = 'Title';
$string['choice_title_help'] = 'Title of the choice. *Attention* all active choices will be displayed while ordered by title.';
$string['edit_choice'] = 'Edit choice';
@@ -206,6 +209,8 @@
$string['publishdate'] = 'Estimated publication date';
$string['runalgorithmbycron'] = 'Automatic allocation after rating period';
$string['runalgorithmbycron_help'] = 'Automatically runs the allocation algorithm after the rating period ended. However, the results have to be published manually.';
+$string['checkbox_generaloption_minsize'] = 'Choices requiring a minimum number of participants';
+$string['checkbox_generaloption_optional'] = 'Some choices are optional';
$string['select_strategy'] = 'Rating strategy';
$string['select_strategy_help'] = 'Choose a rating strategy:
@@ -218,20 +223,33 @@
$string['strategy_not_specified'] = 'You have to select a strategy.';
$string['strategyspecificoptions'] = 'Strategy specific options';
+$string['algorithmoptions'] = 'Algorithm selection';
+$string['algorithm_does_not_support_minsize'] = 'The selected algorithm does not work with the "minimum number of participants" option.';
+$string['algorithm_does_not_support_optional'] = 'The selected algorithm does not work with the "optional choices" option.';
+$string['algorithm_does_not_exist'] = 'The selected algorithm does not exist.';
+
$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}.';
+$string['err_gte'] = 'The minimum number of participants is greater than the maximum number of participants.';
//
//
$string['show_choices_header'] = 'List of all choices';
$string['newchoice'] = 'Add new choice';
$string['choice_table_title'] = 'Title';
$string['choice_table_explanation'] = 'Description';
+$string['choice_table_minsize'] = 'Min. Size';
$string['choice_table_maxsize'] = 'Max. Size';
$string['choice_table_active'] = 'Active';
+$string['choice_table_optional'] = 'Optional';
$string['choice_table_tools'] = 'Edit';
//
+//
+$string['subplugintype_raalgo'] = 'Allocation algorithm';
+$string['subplugintype_raalgo_plural'] = 'Allocation algorithms';
+//
+
$string['is_published'] = 'Published';
$string['strategy_settings_label'] = 'Designation for "{$a}"';
@@ -351,4 +369,9 @@
$string['privacy:metadata:preference:flextable_manual_filter'] = 'Stores the filters that are applied to the manual allocations table.';
$string['filtertabledesc'] = 'Describes the filters that are applied to the allocation table.';
-$string['filtermanualtabledesc'] = 'Describes the filters that are applied to the table of the manual allocation form.';
\ No newline at end of file
+$string['filtermanualtabledesc'] = 'Describes the filters that are applied to the table of the manual allocation form.';
+
+// Persistent class errors.
+$string['error_persistent_ratingallocateid'] = 'The id is invalid.';
+$string['error_persistent_algoname'] = 'The algorithm name is invalid.';
+$string['error_persistent_message'] = 'The message is invalid.';
\ No newline at end of file
diff --git a/locallib.php b/locallib.php
index 040b61a4..243cd56b 100644
--- a/locallib.php
+++ b/locallib.php
@@ -37,10 +37,6 @@
require_once($CFG->dirroot.'/group/lib.php');
require_once(__DIR__.'/classes/algorithm_status.php');
-// Takes care of loading all the solvers.
-require_once(dirname(__FILE__) . '/solver/ford-fulkerson-koegel.php');
-require_once(dirname(__FILE__) . '/solver/edmonds-karp.php');
-
// Now come all the strategies.
require_once(dirname(__FILE__) . '/strategy/strategy01_yes_no.php');
require_once(dirname(__FILE__) . '/strategy/strategy02_yes_maybe_no.php');
@@ -882,10 +878,10 @@ public function distrubute_choices() {
$this->origdbrecord->algorithmstarttime = time();
$this->db->update_record(this_db\ratingallocate::TABLE, $this->origdbrecord);
- $distributor = new solver_edmonds_karp();
+ $distributor = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $this);
// $distributor = new solver_ford_fulkerson();
$timestart = microtime(true);
- $distributor->distribute_users($this);
+ $distributor->distribute_users();
$timeneeded = (microtime(true) - $timestart);
// echo memory_get_peak_usage();
@@ -1484,6 +1480,14 @@ public function get_context() {
return $this->context;
}
+
+ /**
+ * @return int id
+ */
+ public function get_id() {
+ return $this->ratingallocateid;
+ }
+
/**
* @return bool true, if all strategy settings are ok.
*/
@@ -1511,6 +1515,8 @@ public function is_setup_ok() {
* @property string explanation
* @property int $maxsize
* @property bool $active
+ * @property int minsize
+ * @property bool optional
*/
class ratingallocate_choice {
/** @var stdClass original db record */
diff --git a/mod_form.php b/mod_form.php
index 07cb0738..6d095b7c 100644
--- a/mod_form.php
+++ b/mod_form.php
@@ -38,6 +38,7 @@ class mod_ratingallocate_mod_form extends moodleform_mod {
const CHOICE_PLACEHOLDER_IDENTIFIER = 'placeholder_for_choices';
const STRATEGY_OPTIONS = 'strategyopt';
const STRATEGY_OPTIONS_PLACEHOLDER = 'placeholder_strategyopt';
+ const ALGORITHM_OPTIONS_PLACEHOLDER = 'placeholder_algorithmopt';
private $newchoicecounter = 0;
private $msgerrorrequired;
@@ -85,6 +86,14 @@ public function definition() {
// Adding the standard "intro" and "introformat" fields.
$this->standard_intro_elements();
+ // -------------------------------------------------------------------------------
+ // TODO hilfe!
+ // TODO darstellung nebeneinander.
+ $elementname = 'generaloption_minsize';
+ $mform->addElement('advcheckbox', $elementname, get_string('checkbox_' . $elementname, self::MOD_NAME));
+ $elementname = 'generaloption_optional';
+ $mform->addElement('advcheckbox', $elementname, get_string('checkbox_' . $elementname, self::MOD_NAME));
+
// -------------------------------------------------------------------------------
$elementname = 'strategy';
// Define options for select.
@@ -115,7 +124,7 @@ public function definition() {
$mform->setDefault($elementname, 1);
$headerid = 'strategy_fieldset';
- $mform->addElement('header', $headerid, get_string('strategyspecificoptions', ratingallocate_MOD_NAME));
+ $mform->addElement('header', $headerid, get_string('strategyspecificoptions', self::MOD_NAME));
$mform->setExpanded($headerid);
foreach (\strategymanager::get_strategies() as $strategy) {
@@ -132,6 +141,18 @@ public function definition() {
$mform->addElement('static', self::STRATEGY_OPTIONS_PLACEHOLDER.'[' . $strategy . ']', '', '');
}
+ $headerid = 'algorithm_fieldset';
+ $mform->addElement('header', $headerid, get_string('algorithmoptions', self::MOD_NAME));
+ $mform->setExpanded($headerid);
+
+ $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms();
+ foreach ($algorithms as $key => $value) {
+ $features = \mod_ratingallocate\algorithm::get_instance($key, null)->get_supported_features();
+ $mform->addElement('radio', 'algorithm', '', $value, $key, array());
+ }
+ $mform->addRule('algorithm', get_string('err_required', 'form') , 'required', null, 'server');
+ $mform->setDefault('algorithm', array_keys($algorithms)[0]);
+
// Add standard elements, common to all modules.
$this->standard_coursemodule_elements();
@@ -142,7 +163,7 @@ public function definition() {
/**
* Add an settings element to the form. It is enabled only if the strategy it belongs to is selected.
* @param string $stratfieldid id of the element to be added
- * @param array $value array with the element type and its caption
+ * @param array $value array with the element type and its caption
* (usually returned by the strategys get settingsfields methods).
* @param string $strategyid id of the strategy it belongs to.
* @param $mform MoodleQuickForm form object the settings field should be added to.
@@ -247,6 +268,23 @@ public function validation($data, $files) {
}
}
}
+
+ // User has to select an algorithm that exists.
+ if (!empty($data['algorithm'])) {
+ try {
+ $algorithm = \mod_ratingallocate\algorithm::get_instance($data['algorithm'], null);
+ $features = $algorithm->get_supported_features();
+ // User has to select an algorithm that complies to the selected features.
+ if ($data['generaloption_minsize'] && !$features['min']) {
+ $errors['algorithm'] = get_string('algorithm_does_not_support_minsize', self::MOD_NAME);
+ }
+ if ($data['generaloption_optional'] && !$features['opt']) {
+ $errors['algorithm'] = get_string('algorithm_does_not_support_optional', self::MOD_NAME);
+ }
+ } catch (coding_exception $e) {
+ $errors['algorithm'] = get_string('algorithm_does_not_exist', self::MOD_NAME);
+ }
+ }
return $errors;
}
/**
diff --git a/renderer.php b/renderer.php
index 14d6c480..ae1fcbeb 100644
--- a/renderer.php
+++ b/renderer.php
@@ -424,20 +424,22 @@ public function ratingallocate_show_choices_table(ratingallocate $ratingallocate
echo $OUTPUT->single_button($starturl->out(), get_string('newchoice', 'mod_ratingallocate'), 'get');
$table = new \flexible_table('show_ratingallocate_options');
$table->define_baseurl($PAGE->url);
- if ($choicesmodifiably) {
- $table->define_columns(array('title', 'explanation', 'maxsize', 'active', 'tools'));
- $table->define_headers(array(get_string('choice_table_title', 'mod_ratingallocate'),
- get_string('choice_table_explanation', 'mod_ratingallocate'),
- get_string('choice_table_maxsize', 'mod_ratingallocate'),
- get_string('choice_table_active', 'mod_ratingallocate'),
- get_string('choice_table_tools', 'mod_ratingallocate')));
- } else {
- $table->define_columns(array('title', 'explanation', 'maxsize', 'active'));
- $table->define_headers(array(get_string('choice_table_title', 'mod_ratingallocate'),
+
+ $columns = array('title', 'explanation', 'minsize', 'maxsize', 'optional', 'active');
+ $headers = array(get_string('choice_table_title', 'mod_ratingallocate'),
get_string('choice_table_explanation', 'mod_ratingallocate'),
+ get_string('choice_table_minsize', 'mod_ratingallocate'),
get_string('choice_table_maxsize', 'mod_ratingallocate'),
- get_string('choice_table_tools', 'mod_ratingallocate')));
+ get_string('choice_table_optional', 'mod_ratingallocate'),
+ get_string('choice_table_active', 'mod_ratingallocate'));
+
+ if ($choicesmodifiably) {
+ $columns[] = "tools";
+ $headers[] = get_string('choice_table_tools', 'mod_ratingallocate');
}
+
+ $table->define_columns($columns);
+ $table->define_headers($headers);
$table->set_attribute('id', 'mod_ratingallocateshowoptions');
$table->set_attribute('class', 'admintable generaltable');
$table->setup();
@@ -453,7 +455,13 @@ public function ratingallocate_show_choices_table(ratingallocate $ratingallocate
$class = '';
$row[] = $choice->{this_db\ratingallocate_choices::TITLE};
$row[] = $choice->{this_db\ratingallocate_choices::EXPLANATION};
+ $row[] = $choice->{this_db\ratingallocate_choices::MINSIZE};
$row[] = $choice->{this_db\ratingallocate_choices::MAXSIZE};
+ if ($choice->{this_db\ratingallocate_choices::OPTIONAL}) {
+ $row[] = get_string('yes');
+ } else {
+ $row[] = get_string('no');
+ }
if ($choice->{this_db\ratingallocate_choices::ACTIVE}) {
$row[] = get_string('yes');
} else {
diff --git a/solver/edmonds-karp.php b/solver/edmonds-karp.php
deleted file mode 100644
index 746e068a..00000000
--- a/solver/edmonds-karp.php
+++ /dev/null
@@ -1,142 +0,0 @@
-.
-
-/**
- *
- * Contains the algorithm for the distribution
- *
- * @package mod_ratingallocate
- * @subpackage mod_ratingallocate
- * @copyright 2014 M Schulze
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-defined('MOODLE_INTERNAL') || die();
-
-require_once(dirname(__FILE__) . '/../locallib.php');
-require_once(dirname(__FILE__) . '/solver-template.php');
-
-class solver_edmonds_karp extends distributor {
-
- public function get_name() {
- return 'edmonds_karp';
- }
-
- public function compute_distribution($choicerecords, $ratings, $usercount) {
- $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);
-
- $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);
- }
-
- /**
- * Bellman-Ford acc. to Cormen
- *
- * @param $from index of starting node
- * @param $to index of end node
- * @return array with the of the nodes in the path
- */
- private function find_shortest_path_bellf($from, $to) {
- // Table of distances known so far
- $dists = array();
- // Table of predecessors (used to reconstruct the shortest path later)
- $preds = array();
-
- // Number of nodes in the graph
- $count = $this->graph['count'];
-
- // Step 1: initialize graph
- for ($i = 0; $i < $count; $i++) { // for each vertex v in vertices:
- if ($i == $from) {// if v is source then weight[v] := 0
- $dists[$i] = 0;
- } else {// else weight[v] := infinity
- $dists[$i] = INF;
- }
- $preds[$i] = null; // predecessor[v] := null
- }
-
- // Step 2: relax edges repeatedly
- for ($i = 0; $i < $count; $i++) { // for i from 1 to size(vertices)-1:
- $updatedsomething = false;
- foreach ($this->graph as $key => $edges) { // for each edge (u, v) with weight w in edges:
- if (is_array($edges)) {
- foreach ($edges as $key2 => $edge) {
- /* @var $edge edge */
- if ($dists[$edge->from] + $edge->weight < $dists[$edge->to]) { // if weight[u] + w < weight[v]:
- $dists[$edge->to] = $dists[$edge->from] + $edge->weight; // weight[v] := weight[u] + w
- $preds[$edge->to] = $edge->from; // predecessor[v] := u
- $updatedsomething = true;
- }
- }
- }
- }
- if (!$updatedsomething) {
- break; // leave
- }
- }
-
- // Step 3: check for negative-weight cycles
- /*foreach ($graph as $key => $edges) { // for each edge (u, v) with weight w in edges:
- if (is_array($edges)) {
- foreach ($edges as $key2 => $edge) {
-
- if ($dists[$edge->to] + $edge->weight < $dists[$edge->to]) { // if weight[u] + w < weight[v]:
- print_error('negative_cycle', 'ratingallocate');
- }
- }
- }
- }*/
-
- // If there is no path to $to, return null
- if (is_null($preds[$to])) {
- return null;
- }
-
- // cleanup dists to save some space
- unset($dists);
-
- // Use the preds table to reconstruct the shortest path
- $path = array();
- $p = $to;
- while ($p != $from) {
- $path[] = $p;
- $p = $preds[$p];
- }
- $path[] = $from;
- return $path;
- }
-
-}
diff --git a/solver/ford-fulkerson-koegel.php b/solver/ford-fulkerson-koegel.php
deleted file mode 100644
index d8fcd922..00000000
--- a/solver/ford-fulkerson-koegel.php
+++ /dev/null
@@ -1,158 +0,0 @@
-.
-
-/**
- * Internal library of functions for module groupdistribution.
- *
- * Contains the algorithm for the group distribution
- *
- * @package mod_ratingallocate
- * @subpackage mod_ratingallocate originally mod_groupdistribution
- * @copyright 2014 M Schulze
- * @copyright based on code by Stefan Koegel copyright (C) 2013 Stefan Koegel
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-defined('MOODLE_INTERNAL') || die();
-
-require_once(dirname(__FILE__) . '/../locallib.php');
-require_once(dirname(__FILE__) . '/solver-template.php');
-
-class solver_ford_fulkerson extends distributor {
-
- /**
- * Starts the distribution algorithm.
- * Uses the users' ratings and a minimum-cost maximum-flow algorithm
- * to distribute the users fairly into the groups.
- * (see http://en.wikipedia.org/wiki/Minimum-cost_flow_problem)
- * After the algorithm is done, users are removed from their current
- * groups (see clear_all_groups_in_course()) and redistributed
- * according to the computed distriution.
- *
- */
- public function compute_distribution($choicerecords, $ratings, $usercount) {
- $groupdata = array();
- foreach ($choicerecords as $record) {
- $groupdata[$record->id] = $record;
- }
-
- $groupcount = count($groupdata);
- // Index of source and sink in the graph
- $source = 0;
- $sink = $groupcount + $usercount + 1;
- list($fromuserid, $touserid, $fromgroupid, $togroupid) = $this->setup_id_conversions($usercount, $ratings);
-
- $this->setup_graph($groupcount, $usercount, $fromuserid, $fromgroupid, $ratings, $groupdata, $source, $sink);
-
- // Now that the datastructure is complete, we can start the algorithm
- // This is an adaptation of the Ford-Fulkerson algorithm
- // (http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm)
- for ($i = 1; $i <= $usercount; $i++) {
- // Look for an augmenting path (a shortest path from the source to the sink)
- $path = $this->find_shortest_path_bellmanf_koegel($source, $sink);
- // If ther is no such path, it is impossible to fit any more users into groups.
- if (is_null($path)) {
- // Stop the algorithm
- continue;
- }
- // Reverse the augmenting path, thereby distributing a user into a group
- $this->augment_flow($path);
- }
-
- return $this->extract_allocation($touserid, $togroupid);
- }
-
- /**
- * Uses a modified Bellman-Ford algorithm to find a shortest path
- * from $from to $to in $graph. We can't use Dijkstra here, because
- * the graph contains edges with negative weight.
- *
- * @param $from index of starting node
- * @param $to index of end node
- * @return array with the of the nodes in the path
- */
- public function find_shortest_path_bellmanf_koegel($from, $to) {
-
- // Table of distances known so far
- $dists = array();
- // Table of predecessors (used to reconstruct the shortest path later)
- $preds = array();
- // Stack of the edges we need to test next
- $edges = $this->graph[$from];
- // Number of nodes in the graph
- $count = $this->graph['count'];
-
- // To prevent the algorithm from getting stuck in a loop with
- // with negative weight, we stop it after $count ^ 3 iterations
- $counter = 0;
- $limit = $count * $count * $count;
-
- // Initialize dists and preds
- for ($i = 0; $i < $count; $i++) {
- if ($i == $from) {
- $dists[$i] = 0;
- } else {
- $dists[$i] = -INF;
- }
- $preds[$i] = null;
- }
-
- while (!empty($edges) and $counter < $limit) {
- $counter++;
-
- /* @var e edge */
- $e = array_pop($edges);
-
- $f = $e->from;
- $t = $e->to;
- $dist = $e->weight + $dists[$f];
-
- // If this edge improves a distance update the tables and the edges stack
- if ($dist > $dists[$t]) {
- $dists[$t] = $dist;
- $preds[$t] = $f;
- foreach ($this->graph[$t] as $newedge) {
- $edges[] = $newedge;
- }
- }
- }
-
- // A valid groupdistribution graph can't contain a negative edge
- if ($counter == $limit) {
- print_error('negative_cycle', 'ratingallocate');
- }
-
- // If there is no path to $to, return null
- if (is_null($preds[$to])) {
- return null;
- }
-
- // Use the preds table to reconstruct the shortest path
- $path = array();
- $p = $to;
- while ($p != $from) {
- $path[] = $p;
- $p = $preds[$p];
- }
- $path[] = $from;
-
- return $path;
- }
-
- public function get_name() {
- return "ford-fulkerson Koegel2014";
- }
-
-}
diff --git a/solver/solver-template.php b/solver/solver-template.php
deleted file mode 100644
index ddbab4c7..00000000
--- a/solver/solver-template.php
+++ /dev/null
@@ -1,260 +0,0 @@
-.
-
-/**
- * Internal library of functions for module groupdistribution.
- *
- * Contains the algorithm for the group distribution and some helper functions
- * that wrap useful SQL querys.
- *
- * @package mod_ratingallocate
- * @subpackage mod_ratingallocate
- * @copyright 2014 M Schulze, C Usener
- * @copyright based on code by Stefan Koegel copyright (C) 2013 Stefan Koegel
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-/**
- * Represents an Edge in the graph to have fixed fields instead of array-fields
- */
-class edge {
- /** @var from int */
- public $from;
- /** @var to int */
- public $to;
- /** @var weight int Cost for this edge (rating of user) */
- public $weight;
- /** @var space int (places left for choices) */
- public $space;
-
- public function __construct($from, $to, $weight, $space = 0) {
- $this->from = $from;
- $this->to = $to;
- $this->weight = $weight;
- $this->space = $space;
- }
-
-}
-
-/**
- * Template Class for distribution algorithms
- */
-class distributor {
-
- /** @var $graph Flow-Graph built */
- protected $graph;
-
- /**
- * Compute the 'satisfaction' functions that is to be maximized by adding the
- * ratings users gave to their allocated choices
- * @param array $ratings
- * @param array $distribution
- * @return integer
- */
- public static function compute_target_function($ratings, $distribution) {
- $functionvalue = 0;
- foreach ($distribution as $choiceid => $choice) {
- // $choice ist jetzt ein array von userids
- foreach ($choice as $userid) {
- // jetzt das richtige Rating rausfinden
- foreach ($ratings as $rating) {
- if ($rating->userid == $userid && $rating->choiceid == $choiceid) {
- $functionvalue += $rating->rating;
- continue; // aus der Such-Schleife raus und weitermachen
- }
- }
- }
- }
- return $functionvalue;
- }
-
- /**
- * Entry-point for the \ratingallocate object to call a solver
- * @param \ratingallocate $ratingallocate
- */
- public function distribute_users(\ratingallocate $ratingallocate) {
- // Extend PHP time limit
-// core_php_time_limit::raise();
-
- // Load data from database
- $choicerecords = $ratingallocate->get_rateable_choices();
- $ratings = $ratingallocate->get_ratings_for_rateable_choices();
-
- // Randomize the order of the enrties to prevent advantages for early entry
- shuffle($ratings);
-
- $usercount = count($ratingallocate->get_raters_in_course());
-
- $distributions = $this->compute_distribution($choicerecords, $ratings, $usercount);
-
- $transaction = $ratingallocate->db->start_delegated_transaction(); // perform all allocation manipulation / inserts in one transaction
-
- $ratingallocate->clear_all_allocations();
-
- foreach ($distributions as $choiceid => $users) {
- foreach ($users as $userid) {
- $ratingallocate->add_allocation($choiceid, $userid, $ratingallocate->ratingallocate->id);
- }
- }
- $transaction->allow_commit();
- }
-
- /**
- * Extracts a distribution/allocation from the graph.
- *
- * @param $touserid a map mapping from indexes in the graph to userids
- * @param $tochoiceid a map mapping from indexes in the graph to choiceids
- * @return an array of the form array(groupid => array(userid, ...), ...)
- */
- protected function extract_allocation($touserid, $tochoiceid) {
- $distribution = array();
- foreach ($tochoiceid as $index => $groupid) {
- $group = $this->graph[$index];
- $distribution[$groupid] = array();
- foreach ($group as $assignment) {
- /* @var $assignment edge */
- $user = intval($assignment->to);
- if (array_key_exists($user, $touserid)) {
- $distribution[$groupid][] = $touserid[$user];
- }
- }
- }
- return $distribution;
- }
-
- /**
- * Setup conversions between ids of users and choices to their node-ids in the graph
- * @param type $usercount
- * @param type $ratings
- * @return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid);
- */
- public static function setup_id_conversions($usercount, $ratings) {
- // These tables convert userids to their index in the graph
- // The range is [1..$usercount]
- $fromuserid = array();
- $touserid = array();
- // These tables convert choiceids to their index in the graph
- // The range is [$usercount + 1 .. $usercount + $choicecount]
- $fromchoiceid = array();
- $tochoiceid = array();
-
- // User counter
- $ui = 1;
- // Group counter
- $gi = $usercount + 1;
-
- // Fill the conversion tables for group and user ids
- foreach ($ratings as $rating) {
- if (!array_key_exists($rating->userid, $fromuserid)) {
- $fromuserid[$rating->userid] = $ui;
- $touserid[$ui] = $rating->userid;
- $ui++;
- }
- if (!array_key_exists($rating->choiceid, $fromchoiceid)) {
- $fromchoiceid[$rating->choiceid] = $gi;
- $tochoiceid[$gi] = $rating->choiceid;
- $gi++;
- }
- }
-
- return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid);
- }
-
- /**
- * Sets up $this->graph
- * @param type $choicecount
- * @param type $usercount
- * @param type $fromuserid
- * @param type $fromchoiceid
- * @param type $ratings
- * @param type $choicedata
- * @param type $source
- * @param type $sink
- */
- protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, $weightmult = 1) {
- // Construct the datastructures for the algorithm
- // A directed weighted bipartite graph.
- // A source is connected to all users with unit cost.
- // The users are connected to their choices with cost equal to their rating.
- // The choices are connected to a sink with 0 cost
- $this->graph = array();
- // Add source, sink and number of nodes to the graph
- $this->graph[$source] = array();
- $this->graph[$sink] = array();
- $this->graph['count'] = $choicecount + $usercount + 2;
-
- // Add users and choices to the graph and connect them to the source and sink
- foreach ($fromuserid as $id => $user) {
- $this->graph[$user] = array();
- $this->graph[$source][] = new edge($source, $user, 0);
- }
- foreach ($fromchoiceid as $id => $choice) {
- $this->graph[$choice] = array();
- $this->graph[$choice][] = new edge($choice, $sink, 0, $choicedata[$id]->maxsize);
- }
-
- // Add the edges representing the ratings to the graph
- foreach ($ratings as $id => $rating) {
- $user = $fromuserid[$rating->userid];
- $choice = $fromchoiceid[$rating->choiceid];
- $weight = $rating->rating;
- if ($weight > 0) {
- $this->graph[$user][] = new edge($user, $choice, $weightmult * $weight);
- }
- }
- }
-
- /**
- * Augments the flow in the network, i.e. augments the overall 'satisfaction'
- * by distributing users to choices
- * Reverses all edges along $path in $graph
- * @param type $path path from t to s
- */
- protected function augment_flow($path) {
- if (is_null($path) or count($path) < 2) {
- print_error('invalid_path', 'ratingallocate');
- }
-
- // Walk along the path, from s to t
- for ($i = count($path) - 1; $i > 0; $i--) {
- $from = $path[$i];
- $to = $path[$i - 1];
- $edge = null;
- $foundedgeid = -1;
- // Find the edge
- foreach ($this->graph[$from] as $index => &$edge) {
- /* @var $edge edge */
- if ($edge->to == $to) {
- $foundedgeid = $index;
- break;
- }
- }
- // The second to last node in a path has to be a choice-node.
- // Reduce its space by one, because one user just got distributed into it.
- if ($i == 1 and $edge->space > 1) {
- $edge->space --;
- } else {
- // Remove the edge
- array_splice($this->graph[$from], $foundedgeid, 1);
- // Add a new edge in the opposite direction whose weight has an opposite sign
- // array_push($this->graph[$to], new edge($to, $from, -1 * $edge->weight));
- // according to php doc, this is faster
- $this->graph[$to][] = new edge($to, $from, -1 * $edge->weight);
- }
- }
- }
-
-}
diff --git a/tests/behat/allocation_status.feature b/tests/behat/allocation_status.feature
index cf56064d..a5fb0a59 100644
--- a/tests/behat/allocation_status.feature
+++ b/tests/behat/allocation_status.feature
@@ -6,20 +6,20 @@ Feature: Students should get status information according to their rating and th
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
- | username | firstname | lastname | email |
- | teacher1 | Theo | Teacher | teacher1@example.com |
- | student1 | Steve | Student | student1@example.com |
- | student2 | Sophie | Student | student2@example.com |
- | student3 | Steffanie | Student | student3@example.com |
+ | username | firstname | lastname | email |
+ | teacher1 | Theo | Teacher | teacher1@example.com |
+ | student1 | Steve | Student | student1@example.com |
+ | student2 | Sophie | Student | student2@example.com |
+ | student3 | Steffanie | Student | student3@example.com |
And the following "course enrolments" exist:
- | user | course | role |
- | teacher1 | C1 | editingteacher |
- | student1 | C1 | student |
- | student2 | C1 | student |
- | student3 | C1 | student |
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
+ | student2 | C1 | student |
+ | student3 | C1 | student |
And the following "activities" exist:
- | activity | course | idnumber | name | accesstimestart |
- | ratingallocate | C1 | ra1 | My Fair Allocation | ##yesterday## |
+ | activity | course | idnumber | name | accesstimestart |
+ | ratingallocate | C1 | ra1 | My Fair Allocation | ##yesterday## |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "My Fair Allocation"
@@ -27,7 +27,8 @@ Feature: Students should get status information according to their rating and th
And I add a new choice with the values:
| title | My only choice |
| explanation | Test |
- | maxsize | 1 |
+ | maxsize | 1 |
+ | minsize | 0 |
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
@@ -47,12 +48,12 @@ Feature: Students should get status information according to their rating and th
And I follow "My Fair Allocation"
And I navigate to "Edit settings" in current page administration
And I set the following fields to these values:
- | accesstimestart[day] | ##2 days ago##j## |
+ | accesstimestart[day] | ##2 days ago##j## |
| accesstimestart[month] | ##2 days ago##n## |
- | accesstimestart[year] | ##2 days ago##Y## |
- | accesstimestop[day] | ##yesterday##j## |
- | accesstimestop[month] | ##yesterday##n## |
- | accesstimestop[year] | ##yesterday##Y## |
+ | accesstimestart[year] | ##2 days ago##Y## |
+ | accesstimestop[day] | ##yesterday##j## |
+ | accesstimestop[month] | ##yesterday##n## |
+ | accesstimestop[year] | ##yesterday##Y## |
And I press "id_submitbutton"
And I run the scheduled task "mod_ratingallocate\task\cron_task"
And I am on "Course 1" course homepage
diff --git a/tests/behat/behat_mod_ratingallocate.php b/tests/behat/behat_mod_ratingallocate.php
index 1ec650e3..d26961bd 100644
--- a/tests/behat/behat_mod_ratingallocate.php
+++ b/tests/behat/behat_mod_ratingallocate.php
@@ -32,6 +32,12 @@ public function i_set_the_values_of_the_choice_to(TableNode $choicedata) {
} else {
$this->execute('behat_mod_ratingallocate::i_uncheck_the_active_checkbox');
}
+ } else if($locator === 'optional') {
+ if ($value === 'true') {
+ $this->execute('behat_mod_ratingallocate::i_check_the_optional_checkbox');
+ } else {
+ $this->execute('behat_mod_ratingallocate::i_uncheck_the_optional_checkbox');
+ }
} else {
$this->execute('behat_forms::i_set_the_field_to', array($locator, $value));
}
@@ -190,6 +196,26 @@ public function i_uncheck_the_active_checkbox() {
$checkbox->uncheck();
}
+ /**
+ * Checks the optional checkbox.
+ *
+ * @Given /^I check the optional checkbox$/
+ */
+ public function i_check_the_optional_checkbox() {
+ $checkbox = $this->find_field("id_optional");
+ $checkbox->check();
+ }
+
+ /**
+ * Unchecks the optional checkbox.
+ *
+ * @Given /^I uncheck the optional checkbox$/
+ */
+ public function i_uncheck_the_optional_checkbox() {
+ $checkbox = $this->find_field("id_optional");
+ $checkbox->uncheck();
+ }
+
/**
* The choice with id should be active.
*
@@ -223,6 +249,39 @@ public function the_choice_should_not_be_active($title) {
}
}
+ /**
+ * The choice with id should be optional.
+ *
+ * @Then /^the choice with name "([^"]*)" should be optional$/
+ *
+ * @throws ExpectationException
+ * @param string $title title of the choice
+ */
+ public function the_choice_should_be_optional($title) {
+ $choice = $this->get_choice($title);
+ if (!$choice->optional) {
+ throw new ExpectationException('The choice "' . $title .
+ '" should be optional.',
+ $this->getSession());
+ }
+ }
+
+ /**
+ * The choice with id should not be optional.
+ *
+ * @Then /^the choice with name "([^"]*)" should not be optional$/
+ *
+ * @throws ExpectationException
+ * @param string $title title of the choice
+ */
+ public function the_choice_should_not_be_optional($title) {
+ $choice = $this->get_choice($title);
+ if ($choice->optional) {
+ throw new ExpectationException('The choice "' . $title. '" should not be optional',
+ $this->getSession());
+ }
+ }
+
/**
*
*
diff --git a/tests/behat/defaultratings.feature b/tests/behat/defaultratings.feature
index 5469e90e..fcf1e4e6 100644
--- a/tests/behat/defaultratings.feature
+++ b/tests/behat/defaultratings.feature
@@ -7,16 +7,16 @@ Feature: When a student starts a rating the default values of all choices
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
- | username | firstname | lastname | email |
- | teacher1 | Teacher | 1 | teacher1@example.com |
- | student1 | Student | 1 | student1@example.com |
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
- | user | course | role |
- | teacher1 | C1 | editingteacher |
- | student1 | C1 | student |
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
And the following "activities" exist:
- | activity | course | idnumber | name |
- | ratingallocate | C1 | ra1 | My Fair Allocation |
+ | activity | course | idnumber | name |
+ | ratingallocate | C1 | ra1 | My Fair Allocation |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "My Fair Allocation"
@@ -24,11 +24,13 @@ Feature: When a student starts a rating the default values of all choices
And I add a new choice with the values:
| title | My first choice |
| explanation | Test 1 |
- | maxsize | 2 |
+ | maxsize | 2 |
+ | minsize | 0 |
And I add a new choice with the values:
| title | My second choice |
- | explanation | Test 1 |
- | maxsize | 2 |
+ | explanation | Test 1 |
+ | maxsize | 2 |
+ | minsize | 0 |
@javascript
Scenario: The default rating is the max rating
@@ -41,10 +43,10 @@ Feature: When a student starts a rating the default values of all choices
And I follow "My Fair Allocation"
And I press "Edit Rating"
Then I should see the following rating form:
- | My first choice | 4 |
+ | My first choice | 4 |
| My second choice | 4 |
- @javascript @wip
+ @javascript
Scenario: The default rating should be changeable to a medium rating
And I navigate to "Edit settings" in current page administration
And I select "strategy_lickert" from the "strategy" singleselect
@@ -56,10 +58,10 @@ Feature: When a student starts a rating the default values of all choices
And I follow "My Fair Allocation"
And I press "Edit Rating"
Then I should see the following rating form:
- | My first choice | 3 |
+ | My first choice | 3 |
| My second choice | 3 |
- @javascript @wip
+ @javascript
Scenario: The default rating should be changeable to the lowest rating
And I navigate to "Edit settings" in current page administration
And I select "strategy_lickert" from the "strategy" singleselect
@@ -71,7 +73,7 @@ Feature: When a student starts a rating the default values of all choices
And I follow "My Fair Allocation"
And I press "Edit Rating"
Then I should see the following rating form:
- | My first choice | 0 |
+ | My first choice | 0 |
| My second choice | 0 |
@javascript
@@ -85,10 +87,10 @@ Feature: When a student starts a rating the default values of all choices
And I follow "My Fair Allocation"
And I press "Edit Rating"
And I set the rating form to the following values:
- | My first choice | 2 |
+ | My first choice | 2 |
| My second choice | 3 |
And I press "Save changes"
And I press "Edit Rating"
Then I should see the following rating form:
- | My first choice | 2 |
+ | My first choice | 2 |
| My second choice | 3 |
\ No newline at end of file
diff --git a/tests/behat/mod_form.feature b/tests/behat/mod_form.feature
index 20ade79d..acdc2c73 100644
--- a/tests/behat/mod_form.feature
+++ b/tests/behat/mod_form.feature
@@ -7,13 +7,13 @@ Feature: Creating a new rating allocation, where new choices need to
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
- | username | firstname | lastname | email |
- | teacher1 | Teacher | 1 | teacher1@example.com |
- | student1 | Student | 1 | student1@example.com |
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
- | user | course | role |
- | teacher1 | C1 | editingteacher |
- | student1 | C1 | student |
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I add a "Fair Allocation" to section "0" and I fill the form with:
@@ -23,21 +23,25 @@ Feature: Creating a new rating allocation, where new choices need to
And I add a new choice with the values:
| title | My first choice |
| explanation | Test 1 |
- | maxsize | 2 |
+ | maxsize | 2 |
+ | minsize | 0 |
And I add a new choice with the values:
| title | My second choice |
| explanation | Test 2 |
- | maxsize | 2 |
+ | maxsize | 2 |
+ | minsize | 0 |
And I add a new choice with the values:
| title | My third choice |
- | explanation | Test 3 |
- | maxsize | 2 |
+ | explanation | Test 3 |
+ | maxsize | 2 |
+ | minsize | 0 |
Scenario: Create a new rating alloation and add an additonal new choice.
Given I add a new choice with the values:
- | title | My fourth choice |
- | explanation | Test 4 |
- | maxsize | 2 |
+ | title | My fourth choice |
+ | explanation | Test 4 |
+ | maxsize | 2 |
+ | minsize | 0 |
Then I should see the choice with the title "My first choice"
And I should see the choice with the title "My second choice"
And I should see the choice with the title "My third choice"
@@ -45,9 +49,9 @@ Feature: Creating a new rating allocation, where new choices need to
Scenario: Create a new rating alloation and add two additonal new choices using the add next button.
Given I add new choices with the values:
- | title | explanation | maxsize |
- | My fourth choice | Test 4 | 2 |
- | My fifth choice | Test 5 | 2 |
+ | title | explanation | maxsize | minsize | optional |
+ | My fourth choice | Test 4 | 2 | 0 | 0 |
+ | My fifth choice | Test 5 | 2 | 0 | 0 |
Then I should see the choice with the title "My first choice"
And I should see the choice with the title "My second choice"
And I should see the choice with the title "My third choice"
@@ -57,9 +61,9 @@ Feature: Creating a new rating allocation, where new choices need to
@javascript
Scenario: Create a new rating alloation and add two additonal new choices, but delete two old and one new.
When I add new choices with the values:
- | title | explanation | maxsize |
- | My fourth choice | Test 4 | 2 |
- | My fifth choice | Test 5 | 2 |
+ | title | explanation | maxsize | minsize | optional |
+ | My fourth choice | Test 4 | 2 | 0 | 0 |
+ | My fifth choice | Test 5 | 2 | 0 | 0 |
And I delete the choice with the title "My first choice"
And I delete the choice with the title "My second choice"
And I delete the choice with the title "My fifth choice"
@@ -73,43 +77,51 @@ Feature: Creating a new rating allocation, where new choices need to
Scenario: Create a new rating alloation and add an additonal new active choice.
When I add a new choice with the values:
| title | My fourth choice |
- | explanation | Test 4 |
- | maxsize | 1337 |
- | active | true |
+ | explanation | Test 4 |
+ | maxsize | 1337 |
+ | active | true |
+ | minsize | 0 |
+ | optional | true |
And I should see the choice with the title "My fourth choice"
And the choice with name "My fourth choice" should have explanation being equal to "Test 4"
And the choice with name "My fourth choice" should have maxsize being equal to 1337
And the choice with name "My fourth choice" should be active
+ And the choice with name "My fourth choice" should be optional
@javascript
Scenario: Create a new rating alloation and add an additonal new inactive choice.
When I add a new choice with the values:
| title | My fourth choice |
- | explanation | Test 4 |
- | maxsize | 1337 |
+ | explanation | Test 4 |
+ | maxsize | 1337 |
| active | false |
+ | minsize | 0 |
+ | optional | false |
And I should see the choice with the title "My fourth choice"
And the choice with name "My fourth choice" should have explanation being equal to "Test 4"
And the choice with name "My fourth choice" should have maxsize being equal to 1337
And the choice with name "My fourth choice" should not be active
+ And the choice with name "My fourth choice" should not be optional
@javascript
Scenario: Create a new rating alloation and add an additonal new inactive choice. Change the the choice to active.
When I add a new choice with the values:
- | title | My fourth choice |
- | explanation | This is my discription |
- | maxsize | 1231243 |
- | active | false |
+ | title | My fourth choice |
+ | explanation | This is my discription |
+ | maxsize | 1231243 |
+ | active | false |
+ | minsize | 0 |
Then I set the choice with the title "My fourth choice" to active
And I should see "My fourth choice"
And the choice with name "My fourth choice" should be active
Scenario: Create a new rating alloation and add an additonal new active choice. Change the the choice to inactive.
When I add a new choice with the values:
- | title | My fourth choice |
- | explanation | This is my discription |
- | maxsize | 1231243 |
- | active | true |
+ | title | My fourth choice |
+ | explanation | This is my discription |
+ | maxsize | 1231243 |
+ | active | true |
+ | minsize | 0 |
Then I set the choice with the title "My fourth choice" to inactive
And I should see "My fourth choice"
And the choice with name "My fourth choice" should not be active
diff --git a/tests/behat/ratings.feature b/tests/behat/ratings.feature
index 374da14a..59c89943 100644
--- a/tests/behat/ratings.feature
+++ b/tests/behat/ratings.feature
@@ -6,16 +6,16 @@ Feature: When a student rates a rating should be saved and it should be possible
| fullname | shortname | category | groupmode |
| Course 1 | C1 | 0 | 1 |
And the following "users" exist:
- | username | firstname | lastname | email |
- | teacher1 | Teacher | 1 | teacher1@example.com |
- | student1 | Student | 1 | student1@example.com |
+ | username | firstname | lastname | email |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ | student1 | Student | 1 | student1@example.com |
And the following "course enrolments" exist:
- | user | course | role |
- | teacher1 | C1 | editingteacher |
- | student1 | C1 | student |
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ | student1 | C1 | student |
And the following "activities" exist:
- | activity | course | idnumber | name |
- | ratingallocate | C1 | ra1 | My Fair Allocation |
+ | activity | course | idnumber | name |
+ | ratingallocate | C1 | ra1 | My Fair Allocation |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I follow "My Fair Allocation"
@@ -23,15 +23,18 @@ Feature: When a student rates a rating should be saved and it should be possible
And I add a new choice with the values:
| title | My first choice |
| explanation | Test 1 |
- | maxsize | 2 |
+ | maxsize | 2 |
+ | minsize | 0 |
And I add a new choice with the values:
| title | My second choice |
| explanation | Test 2 |
- | maxsize | 2 |
+ | maxsize | 2 |
+ | minsize | 0 |
And I add a new choice with the values:
| title | My third choice |
- | explanation | Test 3 |
- | maxsize | 2 |
+ | explanation | Test 3 |
+ | maxsize | 2 |
+ | minsize | 0 |
And I log out
@javascript
diff --git a/tests/generator/lib.php b/tests/generator/lib.php
index 2a610484..7df181d2 100644
--- a/tests/generator/lib.php
+++ b/tests/generator/lib.php
@@ -90,11 +90,13 @@ public static function get_default_choice_data() {
array('title' => 'Choice 1',
'explanation' => 'Some explanatory text for choice 1',
'maxsize' => '10',
- 'active' => true),
+ 'active' => true,
+ 'minsize' => '0'),
array('title' => 'Choice 2',
'explanation' => 'Some explanatory text for choice 2',
'maxsize' => '5',
- 'active' => false
+ 'active' => false,
+ 'minsize' => '0'
)
);
}
diff --git a/tests/mod_generator_test.php b/tests/mod_generator_test.php
index ed734793..093bd19e 100644
--- a/tests/mod_generator_test.php
+++ b/tests/mod_generator_test.php
@@ -62,7 +62,10 @@ public function test_create_instance() {
'accesstimestart' => reset($records)->{'accesstimestart'},
'accesstimestop' => reset($records)->{'accesstimestop'},
'setting' => '{"strategy_yesno":{"maxcrossout":"1"}}',
+ 'generaloption_minsize' => 0,
+ 'generaloption_optional' => 0,
'strategy' => 'strategy_yesno',
+ 'algorithm' => 'edmondskarp',
'publishdate' => reset($records)->{'publishdate'},
'published' => '0',
'notificationsend' => '0',
@@ -85,7 +88,9 @@ public function test_create_instance() {
'ratingallocateid' => $mod->id,
'explanation' => 'Some explanatory text for choice 1',
'maxsize' => '10',
- 'active' => '1'
+ 'minsize' => '0',
+ 'active' => '1',
+ 'optional' => '0'
),
$choice_ids[1] => (object) array(
'title' => 'Choice 2',
@@ -93,7 +98,9 @@ public function test_create_instance() {
'ratingallocateid' => $mod->id,
'explanation' => 'Some explanatory text for choice 2',
'maxsize' => '5',
- 'active' => '0'
+ 'minsize' => '0',
+ 'active' => '0',
+ 'optional' => '0'
)
);
$this->assertEquals($expected_choices, $records);
diff --git a/tests/mod_ratingallocate_algorithm_subplugins_test.php b/tests/mod_ratingallocate_algorithm_subplugins_test.php
new file mode 100644
index 00000000..02322c58
--- /dev/null
+++ b/tests/mod_ratingallocate_algorithm_subplugins_test.php
@@ -0,0 +1,59 @@
+.
+
+/**
+ * Privacy provider tests.
+ *
+ * @package mod_ratingallocate
+ * @copyright 2018 Tamara Gunkel
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+use core_privacy\local\metadata\collection;
+use core_privacy\local\request\deletion_criteria;
+use mod_ratingallocate\privacy\provider;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Privacy provider tests class.
+ *
+ * @package mod_ratingallocate
+ * @copyright 2018 Tamara Gunkel
+ * @group mod_ratingallocate
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_ratingallocate_algorithm_subplugins_testcase extends basic_testcase {
+
+ public function test_default_algorithms_present() {
+ $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms();
+ $this->assertGreaterThanOrEqual(2, count($algorithms));
+ $this->assertArrayHasKey('edmondskarp', $algorithms);
+ $this->assertArrayHasKey('fordfulkersonkoegel', $algorithms);
+ }
+
+ public function test_loads_assignment_subtype() {
+ $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp', null);
+ $this->assertInstanceOf(\mod_ratingallocate\algorithm::class, $algorithm);
+ }
+
+ public function test_algorithm_supported_features() {
+ $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp', null);
+ $supports = $algorithm->get_supported_features();
+ $this->assertArrayHasKey('min', $supports);
+ $this->assertArrayHasKey('opt', $supports);
+ }
+}
diff --git a/tests/mod_ratingallocate_log_test.php b/tests/mod_ratingallocate_log_test.php
new file mode 100644
index 00000000..d7af76c5
--- /dev/null
+++ b/tests/mod_ratingallocate_log_test.php
@@ -0,0 +1,69 @@
+.
+
+/**
+ * Ratingallocate log tests.
+ *
+ * @package mod_ratingallocate
+ * @copyright 2019 R. Tschudi, N Herrmann
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+use core_privacy\local\request\deletion_criteria;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Ratingallocate log tests.
+ *
+ * @package mod_ratingallocate
+ * @copyright 2019 R. Tschudi, N Herrmann
+ * @group mod_ratingallocate
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_ratingallocate_log_test extends advanced_testcase {
+
+ /**
+ * Test the creation of logs.
+ */
+ public function test_algorithm_log_create() {
+ global $DB;
+ $this->resetAfterTest(true);
+
+ // Create minnimal dummy data.
+ $course = $this->getDataGenerator()->create_course();
+ $data = mod_ratingallocate_generator::get_default_values();
+ $data['course'] = $course;
+ $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data);
+ $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec);
+
+ $algorithm = new \mod_ratingallocate\algorithm_testable($ratingallocate);
+ $logs = array('Test Message 0', 'Test Message 1');
+ foreach ($logs as $log) {
+ $algorithm->append_to_log($log);
+ }
+ $entries = $DB->get_records('ratingallocate_execution_log');
+ $first = array_shift($entries);
+ $second = array_shift($entries);
+
+ $this->assertEquals($first->message, 'Test Message 0');
+ $this->assertEquals($second->message, 'Test Message 1');
+ $this->assertEquals($first->ratingallocateid, $ratingallocate->get_id());
+ $this->assertEquals($second->ratingallocateid, $ratingallocate->get_id());
+ $this->assertEquals($first->algorithm, $algorithm->get_subplugin_name());
+ $this->assertEquals($second->algorithm, $algorithm->get_subplugin_name());
+ }
+}
\ No newline at end of file
diff --git a/tests/mod_ratingallocate_solver_test.php b/tests/mod_ratingallocate_solver_test.php
index 1413ea82..9816ab3b 100644
--- a/tests/mod_ratingallocate_solver_test.php
+++ b/tests/mod_ratingallocate_solver_test.php
@@ -28,8 +28,6 @@
global $CFG;
require_once($CFG->dirroot . '/mod/ratingallocate/locallib.php');
-require_once($CFG->dirroot . '/mod/ratingallocate/solver/edmonds-karp.php');
-require_once($CFG->dirroot . '/mod/ratingallocate/solver/ford-fulkerson-koegel.php');
class edmonds_karp_test extends basic_testcase {
@@ -73,9 +71,9 @@ private function perform_race($groupsnum, $ratersnum) {
$usercount = $ratersnum;
- $solvers = array('solver_edmonds_karp', 'solver_ford_fulkerson');
+ $solvers = array('edmondskarp', 'fordfulkersonkoegel');
foreach ($solvers as $solver) {
- $solver1 = new $solver;
+ $solver1 = \mod_ratingallocate\algorithm::get_instance($solver, null);
$timestart = microtime(true);
$distribution1 = $solver1->compute_distribution($groups, $ratings, $usercount);
$result[$solver1->get_name()]['elapsed_sec'] = (microtime(true) - $timestart);
@@ -188,7 +186,7 @@ public function test_edmondskarp() {
$usercount = 5;
- $solver = new solver_edmonds_karp();
+ $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp', null);
$distribution = $solver->compute_distribution($choices, $ratings, $usercount);
$expected = array(1 => array(2, 5), 2 => array(4, 1));
// echo "gesamtpunktzahl: " . $solver->compute_target_function($choices, $ratings, $distribution);
@@ -197,7 +195,7 @@ public function test_edmondskarp() {
$this->assertEquals($solver::compute_target_function($ratings, $distribution), 15);
// test against Koegels solver
- $solverkoe = new solver_ford_fulkerson();
+ $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel', null);
$distributionkoe = $solverkoe->compute_distribution($choices, $ratings, $usercount);
$this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), 15);
$this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), $solver::compute_target_function($ratings, $distribution));
@@ -236,11 +234,11 @@ public function test_negweightcycle() {
$usercount = 2;
- $solver = new solver_edmonds_karp();
+ $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp', null);
$distribution = $solver->compute_distribution($choices, $ratings, $usercount);
$this->assertEquals($solver::compute_target_function($ratings, $distribution), 10);
- $solverkoe = new solver_ford_fulkerson();
+ $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel', null);
$distributionkoe = $solverkoe->compute_distribution($choices, $ratings, $usercount);
$this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), 10);
@@ -269,9 +267,9 @@ public function test_targetfunc() {
$ratings[4]->choiceid = 2;
$ratings[4]->rating = 4;
- $this->assertEquals(distributor::compute_target_function($ratings, array(1 => array(1), 2 => array(2))), 9);
- $this->assertEquals(distributor::compute_target_function($ratings, array(1 => array(1, 2))), 8);
- $this->assertEquals(distributor::compute_target_function($ratings, array(1 => array(2), 2 => array(1))), 7);
+ $this->assertEquals(\mod_ratingallocate\algorithm::compute_target_function($ratings, array(1 => array(1), 2 => array(2))), 9);
+ $this->assertEquals(\mod_ratingallocate\algorithm::compute_target_function($ratings, array(1 => array(1, 2))), 8);
+ $this->assertEquals(\mod_ratingallocate\algorithm::compute_target_function($ratings, array(1 => array(2), 2 => array(1))), 7);
}
/**
@@ -300,7 +298,7 @@ public function test_setupids() {
$ratings[4]->rating = 2;
$usercount = 2;
- list($fromuserid, $touserid, $fromchoiceid, $tochoiceid) = solver_edmonds_karp::setup_id_conversions($usercount, $ratings);
+ list($fromuserid, $touserid, $fromchoiceid, $tochoiceid) = \raalgo_edmondskarp\algorithm_impl::setup_id_conversions($usercount, $ratings);
$this->assertEquals(array(3 => 1, 2 => 2), $fromuserid);
$this->assertEquals(array(1 => 3, 2 => 2), $touserid);
diff --git a/version.php b/version.php
index 2b5c59e8..dd2a6cbe 100644
--- a/version.php
+++ b/version.php
@@ -26,7 +26,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2018112900; // The current module version (Date: YYYYMMDDXX)
+$plugin->version = 2019031916; // The current module version (Date: YYYYMMDDXX)
$plugin->requires = 2017111300; // Requires this Moodle version
$plugin->maturity = MATURITY_STABLE;
$plugin->release = 'v3.6-r1';
diff --git a/view.php b/view.php
index 50b28b4c..2b0af028 100644
--- a/view.php
+++ b/view.php
@@ -30,8 +30,6 @@
require_once(dirname(dirname(dirname(__FILE__))).'/config.php');
require_once(dirname(__FILE__).'/locallib.php');
-require_once(dirname(__FILE__).'/solver/ford-fulkerson-koegel.php');
-
$id = optional_param('id', 0, PARAM_INT); // course_module ID, or
$n = optional_param('m', 0, PARAM_INT); // ratingallocate instance ID - it should be named as the first character of the module