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/>. 16 17 /** 18 * Base time splitting method. 19 * 20 * @package core_analytics 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 */ 24 25 namespace core_analytics\local\time_splitting; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 /** 30 * Base time splitting method. 31 * 32 * @package core_analytics 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 abstract class base { 37 38 /** 39 * @var string 40 */ 41 protected $id; 42 43 /** 44 * The model id. 45 * 46 * @var int 47 */ 48 protected $modelid; 49 50 /** 51 * @var \core_analytics\analysable 52 */ 53 protected $analysable; 54 55 /** 56 * @var array 57 */ 58 protected $ranges = []; 59 60 /** 61 * Define the time splitting methods ranges. 62 * 63 * 'time' value defines when predictions are executed, their values will be compared with 64 * the current time in ready_to_predict. The ranges should be sorted by 'time' in 65 * ascending order. 66 * 67 * @return array('start' => time(), 'end' => time(), 'time' => time()) 68 */ 69 abstract protected function define_ranges(); 70 71 /** 72 * Returns a lang_string object representing the name for the time splitting method. 73 * 74 * Used as column identificator. 75 * 76 * If there is a corresponding '_help' string this will be shown as well. 77 * 78 * @return \lang_string 79 */ 80 public static abstract function get_name() : \lang_string; 81 82 /** 83 * Returns the time splitting method id. 84 * 85 * @return string 86 */ 87 public function get_id() { 88 return '\\' . get_class($this); 89 } 90 91 /** 92 * Assigns the analysable and updates the time ranges according to the analysable start and end dates. 93 * 94 * @param \core_analytics\analysable $analysable 95 * @return void 96 */ 97 public function set_analysable(\core_analytics\analysable $analysable) { 98 $this->analysable = $analysable; 99 $this->ranges = $this->define_ranges(); 100 $this->validate_ranges(); 101 } 102 103 /** 104 * Assigns the model id to this time-splitting method it case it needs it. 105 * 106 * @param int $modelid 107 */ 108 public function set_modelid(int $modelid) { 109 $this->modelid = $modelid; 110 } 111 112 /** 113 * get_analysable 114 * 115 * @return \core_analytics\analysable 116 */ 117 public function get_analysable() { 118 return $this->analysable; 119 } 120 121 /** 122 * Returns whether the course can be processed by this time splitting method or not. 123 * 124 * @param \core_analytics\analysable $analysable 125 * @return bool 126 */ 127 public function is_valid_analysable(\core_analytics\analysable $analysable) { 128 return true; 129 } 130 131 /** 132 * Should we predict this time range now? 133 * 134 * @param array $range 135 * @return bool 136 */ 137 public function ready_to_predict($range) { 138 if ($range['time'] <= time()) { 139 return true; 140 } 141 return false; 142 } 143 144 /** 145 * Should we use this time range for training? 146 * 147 * @param array $range 148 * @return bool 149 */ 150 public function ready_to_train($range) { 151 $now = time(); 152 if ($range['time'] <= $now && $range['end'] <= $now) { 153 return true; 154 } 155 return false; 156 } 157 158 /** 159 * Returns the ranges used by this time splitting method. 160 * 161 * @return array 162 */ 163 public function get_all_ranges() { 164 return $this->ranges; 165 } 166 167 /** 168 * By default all ranges are for training. 169 * 170 * @return array 171 */ 172 public function get_training_ranges() { 173 return $this->ranges; 174 } 175 176 /** 177 * Returns the distinct range indexes in this time splitting method. 178 * 179 * @return int[] 180 */ 181 public function get_distinct_ranges() { 182 if ($this->include_range_info_in_training_data()) { 183 return array_keys($this->ranges); 184 } else { 185 return [0]; 186 } 187 } 188 189 /** 190 * Returns the most recent range that can be used to predict. 191 * 192 * This method is only called when calculating predictions. 193 * 194 * @return array 195 */ 196 public function get_most_recent_prediction_range() { 197 198 $ranges = $this->get_all_ranges(); 199 200 // Opposite order as we are interested in the last range that can be used for prediction. 201 krsort($ranges); 202 203 // We already provided the analysable to the time splitting method, there is no need to feed it back. 204 foreach ($ranges as $rangeindex => $range) { 205 if ($this->ready_to_predict($range)) { 206 // We need to maintain the same indexes. 207 return array($rangeindex => $range); 208 } 209 } 210 211 return array(); 212 } 213 214 /** 215 * Returns range data by its index. 216 * 217 * @param int $rangeindex 218 * @return array|false Range data or false if the index is not part of the existing ranges. 219 */ 220 public function get_range_by_index($rangeindex) { 221 if (!isset($this->ranges[$rangeindex])) { 222 return false; 223 } 224 return $this->ranges[$rangeindex]; 225 } 226 227 /** 228 * Generates a unique sample id (sample in a range index). 229 * 230 * @param int $sampleid 231 * @param int $rangeindex 232 * @return string 233 */ 234 public final function append_rangeindex($sampleid, $rangeindex) { 235 return $sampleid . '-' . $rangeindex; 236 } 237 238 /** 239 * Returns the sample id and the range index from a uniquesampleid. 240 * 241 * @param string $uniquesampleid 242 * @return array array($sampleid, $rangeindex) 243 */ 244 public final function infer_sample_info($uniquesampleid) { 245 return explode('-', $uniquesampleid); 246 } 247 248 /** 249 * Whether to include the range index in the training data or not. 250 * 251 * By default, we consider that the different time ranges included in a time splitting method may not be 252 * compatible between them (i.e. the indicators calculated at the end of the course can easily 253 * differ from indicators calculated at the beginning of the course). So we include the range index as 254 * one of the variables that the machine learning backend uses to generate predictions. 255 * 256 * If the indicators calculated using the different time ranges available in this time splitting method 257 * are comparable you can overwrite this method to return false. 258 * 259 * Note that: 260 * - This is only relevant for models whose predictions are not based on assumptions 261 * (i.e. the ones using a machine learning backend to generate predictions). 262 * - The ranges can only be included in the training data when 263 * we know the final number of ranges the time splitting method will have. E.g. 264 * We can not know the final number of ranges of a 'daily' time splitting method 265 * as we will have one new range every day. 266 * @return bool 267 */ 268 public function include_range_info_in_training_data() { 269 return true; 270 } 271 272 /** 273 * Whether to cache or not the indicator calculations. 274 * 275 * Indicator calculations are stored to be reused across models. The calculations 276 * are indexed by the calculation start and end time, and these times depend on the 277 * time-splitting method. You should overwrite this method and return false if the time 278 * frames generated by your time-splitting method are unique and / or can hardly be 279 * reused by further models. 280 * 281 * @return bool 282 */ 283 public function cache_indicator_calculations(): bool { 284 return true; 285 } 286 287 /** 288 * Is this method valid to evaluate prediction models? 289 * 290 * @return bool 291 */ 292 public function valid_for_evaluation(): bool { 293 return true; 294 } 295 296 /** 297 * Validates the time splitting method ranges. 298 * 299 * @throws \coding_exception 300 * @return void 301 */ 302 protected function validate_ranges() { 303 foreach ($this->ranges as $key => $range) { 304 if (!isset($this->ranges[$key]['start']) || !isset($this->ranges[$key]['end']) || 305 !isset($this->ranges[$key]['time'])) { 306 throw new \coding_exception($this->get_id() . ' time splitting method "' . $key . 307 '" range is not fully defined. We need a start timestamp and an end timestamp.'); 308 } 309 } 310 } 311 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body