Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Algorithm specifyable in subplugin type #175

Open
wants to merge 80 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
4761bb5
added sublugin type
NinaHerrmann Mar 18, 2019
140132e
fixed typo
NinaHerrmann Mar 18, 2019
e03779b
Added formelement for minsize
justusdieckmann Mar 18, 2019
2a355e8
defined subplugin interface
NinaHerrmann Mar 18, 2019
7839ffd
WIP add databasefield for minsize
justusdieckmann Mar 18, 2019
e29e4dc
save min into db
justusdieckmann Mar 18, 2019
bdcac4a
added edmondskarp as subplugin
NinaHerrmann Mar 18, 2019
ec23141
set previous min field in form
justusdieckmann Mar 18, 2019
3e6db5f
changed reference in locallib to subplugin and coing style fixes in s…
NinaHerrmann Mar 18, 2019
53425c5
coding style fixes in subplugin edmondskarp
NinaHerrmann Mar 18, 2019
23bfd66
use subplugin for test
Dagefoerde Mar 18, 2019
8c1e0f6
cleanup imports and fields
Dagefoerde Mar 18, 2019
710f9ee
insert min number of participants into list of all choices table.
justusdieckmann Mar 18, 2019
5251239
load algorithm dynamically
Dagefoerde Mar 18, 2019
2170ad8
added minsize to test
justusdieckmann Mar 18, 2019
425e50f
added optional setting
justusdieckmann Mar 18, 2019
da9e112
add column for optional field in list of all choices table
justusdieckmann Mar 18, 2019
65e595f
adjusted testcases for optional field
justusdieckmann Mar 18, 2019
cb77a28
Add fordfulkersonkoegel algorithm
NinaHerrmann Mar 18, 2019
595a9a9
remove reference to obsolete algo file
NinaHerrmann Mar 18, 2019
2a6131a
fix PHPCS in new files
Dagefoerde Mar 18, 2019
2e71639
Add some further style fixes
Dagefoerde Mar 18, 2019
c321e16
Merge branch 'feature/subplugin' into projekttage
Dagefoerde Mar 18, 2019
e754a0d
Fix behat tests by adding minsize and optional fields to choices
justusdieckmann Mar 18, 2019
1c7674f
Add tests for subplugin management
Dagefoerde Mar 18, 2019
0530be0
Use gte instead of gt
Dagefoerde Mar 18, 2019
6ae7345
added optional support to behat steps
justusdieckmann Mar 19, 2019
b44f1a1
added minsize checkbox
NinaHerrmann Mar 19, 2019
ae36c39
added optional checkbox
NinaHerrmann Mar 19, 2019
a588984
testing checkbox for optional setting
justusdieckmann Mar 19, 2019
8c0747f
not wip anymore
justusdieckmann Mar 19, 2019
1b4fa96
edit db schema
NinaHerrmann Mar 19, 2019
5347faf
Add language strings for general options
NinaHerrmann Mar 19, 2019
36696a6
Merge branch 'feature/min-capacity' into projekttage
justusdieckmann Mar 19, 2019
057fb91
Merge remote-tracking branch 'origin/feature/selectalgo' into projekt…
justusdieckmann Mar 19, 2019
766e7fd
added abstract function for getting supported features in class algor…
NinaHerrmann Mar 19, 2019
15c1702
implemented get supported features in subplugins
NinaHerrmann Mar 19, 2019
f500122
Bumped version number
NinaHerrmann Mar 19, 2019
698d211
added minsize checkbox
NinaHerrmann Mar 19, 2019
439c44e
added optional checkbox
NinaHerrmann Mar 19, 2019
80b9549
merged projekttage
NinaHerrmann Mar 19, 2019
b1df3bc
Add language strings for general options
NinaHerrmann Mar 19, 2019
70281a7
Bumped version number
NinaHerrmann Mar 19, 2019
5229c43
Merge branch 'feature/algorithmselect' of github.com:learnweb/moodle-…
NinaHerrmann Mar 19, 2019
0e28420
Merge branch 'feature/abstractclass_extension_min_opt' into feature/a…
NinaHerrmann Mar 19, 2019
566e404
Form: add algorithm options
NinaHerrmann Mar 19, 2019
d6db13a
Algorithms: return supported features statically
NinaHerrmann Mar 19, 2019
b18991b
Form: add language string for section
NinaHerrmann Mar 19, 2019
33e1388
Form: set algorithm as required
NinaHerrmann Mar 19, 2019
bc8472e
Algorithm: changed interface for supported features
NinaHerrmann Mar 19, 2019
0c81ada
Form: disable depending on supported features
NinaHerrmann Mar 19, 2019
9ec259f
Form: send algorithm name and disable does not work.
NinaHerrmann Mar 19, 2019
a2e5cf6
algorithms: verify that supported_features return correct structure
NinaHerrmann Mar 19, 2019
0a4a566
form: use local constant instead of global one
NinaHerrmann Mar 19, 2019
8e76b9e
form: validate choice of algorithm
NinaHerrmann Mar 19, 2019
3d63020
db: add field for persisting selected algorithm
NinaHerrmann Mar 19, 2019
c20d863
tests: add new instance config fields
NinaHerrmann Mar 19, 2019
8b37b97
test: containsOnlyInstancesOf does not work well
NinaHerrmann Mar 19, 2019
6afc49b
form: set first algorithm as default
NinaHerrmann Mar 19, 2019
d55b2ff
fix code style
NinaHerrmann Mar 19, 2019
44e05d3
test: fix copy-paste problem in tests of new fields
NinaHerrmann Mar 19, 2019
3e4b306
Merge branch 'feature/algorithmselect' into projekttage
NinaHerrmann Mar 19, 2019
5a69529
Add table for logging
justusdieckmann Mar 19, 2019
056aa17
Add persistent for execution_log
justusdieckmann Mar 19, 2019
c164e84
skeleton for appending log and subplugin name abstract function
justusdieckmann Mar 19, 2019
0c7dddb
started append to log function
justusdieckmann Mar 19, 2019
2ef6284
Passing and saving ratingallocate to algorithm in constructor method
justusdieckmann Mar 19, 2019
7e86d0f
generated ratingallocate dummy instance for tests
justusdieckmann Mar 19, 2019
473be27
Added getter for ratingallocateid
justusdieckmann Mar 19, 2019
7795132
Test: created initial data for logging
justusdieckmann Mar 19, 2019
d5fd0b8
Test: added testclass for algorithm
justusdieckmann Mar 19, 2019
38a3334
Test: finished testcase creation for append_to_log
justusdieckmann Mar 19, 2019
434ef91
CodeStyle fixes
justusdieckmann Mar 19, 2019
fba4249
Added cutom error message for persistent class
justusdieckmann Mar 19, 2019
28d6759
Reordered savepoints in upgrade.php
justusdieckmann Mar 19, 2019
30107d7
implemented get_supported_features() in algorithm_testable
justusdieckmann Mar 19, 2019
ba9d4a5
bugfix missing parameter for get_instance()
justusdieckmann Mar 19, 2019
37150e2
remove generating ratingallocate instance because not necessary
justusdieckmann Mar 20, 2019
53b4779
Merge pull request #170 from learnweb/feature/logs
justusdieckmann Mar 20, 2019
379255e
Include Moodle36 in .travis.yml
justusdieckmann Mar 20, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand Down
299 changes: 299 additions & 0 deletions algorithm/edmondskarp/classes/algorithm_impl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
*
* 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];
}
}
49 changes: 49 additions & 0 deletions algorithm/edmondskarp/classes/edge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
*
* 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;
}

}
Loading