Merge branch 'MDL-37810-master' of https://github.com/snake/moodle
[moodle.git] / lib / mlbackend / python / classes / processor.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Python predictions processor
19  *
20  * @package   mlbackend_python
21  * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
22  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 namespace mlbackend_python;
27 defined('MOODLE_INTERNAL') || die();
29 /**
30  * Python predictions processor.
31  *
32  * @package   mlbackend_python
33  * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
34  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class processor implements  \core_analytics\classifier, \core_analytics\regressor {
38     /**
39      * The required version of the python package that performs all calculations.
40      */
41     const REQUIRED_PIP_PACKAGE_VERSION = '0.0.2';
43     /**
44      * The path to the Python bin.
45      *
46      * @var string
47      */
48     protected $pathtopython;
50     /**
51      * The constructor.
52      */
53     public function __construct() {
54         global $CFG;
56         // Set the python location if there is a value.
57         if (!empty($CFG->pathtopython)) {
58             $this->pathtopython = $CFG->pathtopython;
59         }
60     }
62     /**
63      * Is the plugin ready to be used?.
64      *
65      * @return bool|string Returns true on success, a string detailing the error otherwise
66      */
67     public function is_ready() {
68         if (empty($this->pathtopython)) {
69             $settingurl = new \moodle_url('/admin/settings.php', array('section' => 'systempaths'));
70             return get_string('pythonpathnotdefined', 'mlbackend_python', $settingurl->out());
71         }
73         // Check the installed pip package version.
74         $cmd = "{$this->pathtopython} -m moodlemlbackend.version";
76         $output = null;
77         $exitcode = null;
78         // Execute it sending the standard error to $output.
79         $result = exec($cmd . ' 2>&1', $output, $exitcode);
81         if ($result === self::REQUIRED_PIP_PACKAGE_VERSION) {
82             return true;
83         }
85         if ($exitcode != 0) {
86             return get_string('pythonpackagenotinstalled', 'mlbackend_python', $cmd);
87         }
89         if ($result) {
90             $a = (object)array('installed' => $result, 'required' => self::REQUIRED_PIP_PACKAGE_VERSION);
91             return get_string('packageinstalledshouldbe', 'mlbackend_python', $a);
92         }
94         return get_string('pythonpackagenotinstalled', 'mlbackend_python', $cmd);
95     }
97     /**
98      * Trains a machine learning algorithm with the provided dataset.
99      *
100      * @param string $uniqueid
101      * @param \stored_file $dataset
102      * @param string $outputdir
103      * @return \stdClass
104      */
105     public function train_classification($uniqueid, \stored_file $dataset, $outputdir) {
107         // Obtain the physical route to the file.
108         $datasetpath = $this->get_file_path($dataset);
110         $cmd = "{$this->pathtopython} -m moodlemlbackend.training " .
111             escapeshellarg($uniqueid) . ' ' .
112             escapeshellarg($outputdir) . ' ' .
113             escapeshellarg($datasetpath);
115         if (!PHPUNIT_TEST && CLI_SCRIPT) {
116             debugging($cmd, DEBUG_DEVELOPER);
117         }
119         $output = null;
120         $exitcode = null;
121         $result = exec($cmd, $output, $exitcode);
123         if (!$result) {
124             throw new \moodle_exception('errornopredictresults', 'analytics');
125         }
127         if (!$resultobj = json_decode($result)) {
128             throw new \moodle_exception('errorpredictwrongformat', 'analytics', '', json_last_error_msg());
129         }
131         if ($exitcode != 0) {
132             throw new \moodle_exception('errorpredictionsprocessor', 'analytics', '', implode(', ', $resultobj->errors));
133         }
135         return $resultobj;
136     }
138     /**
139      * Classifies the provided dataset samples.
140      *
141      * @param string $uniqueid
142      * @param \stored_file $dataset
143      * @param string $outputdir
144      * @return \stdClass
145      */
146     public function classify($uniqueid, \stored_file $dataset, $outputdir) {
148         // Obtain the physical route to the file.
149         $datasetpath = $this->get_file_path($dataset);
151         $cmd = "{$this->pathtopython} -m moodlemlbackend.prediction " .
152             escapeshellarg($uniqueid) . ' ' .
153             escapeshellarg($outputdir) . ' ' .
154             escapeshellarg($datasetpath);
156         if (!PHPUNIT_TEST && CLI_SCRIPT) {
157             debugging($cmd, DEBUG_DEVELOPER);
158         }
160         $output = null;
161         $exitcode = null;
162         $result = exec($cmd, $output, $exitcode);
164         if (!$result) {
165             throw new \moodle_exception('errornopredictresults', 'analytics');
166         }
168         if (!$resultobj = json_decode($result)) {
169             throw new \moodle_exception('errorpredictwrongformat', 'analytics', '', json_last_error_msg());
170         }
172         if ($exitcode != 0) {
173             throw new \moodle_exception('errorpredictionsprocessor', 'analytics', '', implode(', ', $resultobj->errors));
174         }
176         return $resultobj;
177     }
179     /**
180      * Evaluates this processor classification model using the provided supervised learning dataset.
181      *
182      * @param string $uniqueid
183      * @param float $maxdeviation
184      * @param int $niterations
185      * @param \stored_file $dataset
186      * @param string $outputdir
187      * @return \stdClass
188      */
189     public function evaluate_classification($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir) {
191         // Obtain the physical route to the file.
192         $datasetpath = $this->get_file_path($dataset);
194         $cmd = "{$this->pathtopython} -m moodlemlbackend.evaluation " .
195             escapeshellarg($uniqueid) . ' ' .
196             escapeshellarg($outputdir) . ' ' .
197             escapeshellarg($datasetpath) . ' ' .
198             escapeshellarg(\core_analytics\model::MIN_SCORE) . ' ' .
199             escapeshellarg($maxdeviation) . ' ' .
200             escapeshellarg($niterations);
202         if (!PHPUNIT_TEST && CLI_SCRIPT) {
203             debugging($cmd, DEBUG_DEVELOPER);
204         }
206         $output = null;
207         $exitcode = null;
208         $result = exec($cmd, $output, $exitcode);
210         if (!$result) {
211             throw new \moodle_exception('errornopredictresults', 'analytics');
212         }
214         if (!$resultobj = json_decode($result)) {
215             throw new \moodle_exception('errorpredictwrongformat', 'analytics', '', json_last_error_msg());
216         }
218         return $resultobj;
219     }
221     /**
222      * Train this processor regression model using the provided supervised learning dataset.
223      *
224      * @throws new \coding_exception
225      * @param string $uniqueid
226      * @param \stored_file $dataset
227      * @param string $outputdir
228      * @return \stdClass
229      */
230     public function train_regression($uniqueid, \stored_file $dataset, $outputdir) {
231         throw new \coding_exception('This predictor does not support regression yet.');
232     }
234     /**
235      * Estimates linear values for the provided dataset samples.
236      *
237      * @throws new \coding_exception
238      * @param string $uniqueid
239      * @param \stored_file $dataset
240      * @param mixed $outputdir
241      * @return void
242      */
243     public function estimate($uniqueid, \stored_file $dataset, $outputdir) {
244         throw new \coding_exception('This predictor does not support regression yet.');
245     }
247     /**
248      * Evaluates this processor regression model using the provided supervised learning dataset.
249      *
250      * @throws new \coding_exception
251      * @param string $uniqueid
252      * @param float $maxdeviation
253      * @param int $niterations
254      * @param \stored_file $dataset
255      * @param string $outputdir
256      * @return \stdClass
257      */
258     public function evaluate_regression($uniqueid, $maxdeviation, $niterations, \stored_file $dataset, $outputdir) {
259         throw new \coding_exception('This predictor does not support regression yet.');
260     }
262     /**
263      * Returns the path to the dataset file.
264      *
265      * @param \stored_file $file
266      * @return string
267      */
268     protected function get_file_path(\stored_file $file) {
269         // From moodle filesystem to the local file system.
270         // This is not ideal, but there is no read access to moodle filesystem files.
271         return $file->copy_content_to_temp('core_analytics');
272     }