Completed
Push — master ( 93f96a...71feda )
by claudio
06:25
created

Solver::setMeetingsAvailability()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 15
ccs 12
cts 12
cp 1
rs 9.2
c 1
b 1
f 0
cc 4
eloc 11
nc 4
nop 1
crap 4
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: Claudio Cardinale <[email protected]>
5
 * Date: 07/12/15
6
 * Time: 21.18
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
 */
19
20
namespace plunner\Console\Commands\Optimise;
21
22
use \Illuminate\Console\Scheduling\Schedule;
23
use \Illuminate\Foundation\Application;
24
25
/**
26
 * Class Solver
27
 * @package plunner\Console\Commands\Optimise
28
 * @author Claudio Cardinale <[email protected]>
29
 * @copyright 2015 Claudio Cardinale
30
 * @version 1.0.0
31
 */
32
class Solver
33
{
34
    /**
35
     * @var Path
36
     */
37
    private $path;
38
    /**
39
     * @var string[]
40
     */
41
    private $users;
42
    /**
43
     * @var string[]
44
     */
45
    private $meetings;
46
    /**
47
     * @var int
48
     */
49
    private $timeSlots = 0;
50
    /**
51
     * @var int
52
     */
53
    private $maxTimeSlots = 0;
54
    /**
55
     * @var string[]
56
     */
57
    private $meetingsAvailability;
58
    /**
59
     * @var string[]
60
     */
61
    private $meetingsDuration;
62
    /**
63
     * @var string[]
64
     */
65
    private $usersAvailability;
66
    /**
67
     * @var string[]
68
     */
69
    private $usersMeetings;
70
71
    /**
72
    * @var Schedule laravel schedule object needed to perform command in background
73
    */
74
    private $schedule;
75
76
    /**
77
     * @var Application
78
     */
79
    private $laravel;
80
81
    //TODo clone function rmemeber to clone also path or create a new one
82
    //TODO mehtod to check if all variables are correctly set
83
    //TODO check no duplicates
84
    //TODO exception if glpsol return erros
85
    //TODO intercept all erros of system calls like mkdir
86
    //TODO to_string
87
88
    /**
89
     * Solver constructor.
90
     * @param Schedule $schedule
91
     * @param Application $laravel
92
     * @throws OptimiseException on general problems
93
     */
94 4
    public function __construct(Schedule $schedule, Application $laravel)
95
    {
96 4
        self::checkGlpsol();
97 4
        $this->path = Path::createPath();
98 4
        $this->schedule = $schedule;
99 4
        $this->laravel = $laravel;
100 4
    }
101
102
    /**
103
     * @throws OptimiseException
104
     */
105
    public function __destruct()
106
    {
107
        $this->path = null; //call the path destruct
108
    }
109
110
    /**
111
     * @throws OptimiseException
112
     */
113 4
    static private function checkGlpsol()
114
    {
115 4
        if(!(`which glpsol`))
116 4
            throw new OptimiseException('glpsol is not installed');
117 4
    }
118
119
    /**
120
     * @return Path
121
     */
122
    public function getPath()
123
    {
124
        return clone $this->path;
125
    }
126
127
    /**
128
     * @return Schedule
129
     */
130
    public function getSchedule()
131
    {
132
        return $this->schedule;
133
    }
134
135
    /**
136
     * @param Schedule $schedule
137
     * @return Solver
138
     */
139
    public function setSchedule($schedule)
140
    {
141
        $this->schedule = $schedule;
142
        return $this;
143
    }
144
145
    /**
146
     * @return \string[]
147
     */
148 2
    public function getUsers()
149
    {
150 2
        return $this->users;
151
    }
152
153
    /**
154
     * @param string[] $users
155
     * @return Solver
156
     */
157 4
    public function setUsers($users)
158
    {
159 4
        $this->users = $users;
160 4
        return $this;
161
    }
162
163
    /**
164
     * @return string[]
165
     */
166 2
    public function getMeetings()
167
    {
168 2
        return $this->meetings;
169
    }
170
171
    /**
172
     * @param string[] $meetings
173
     * @return Solver
174
     */
175 4
    public function setMeetings($meetings)
176
    {
177 4
        $this->meetings = $meetings;
178 4
        return $this;
179
    }
180
181
    /**
182
     * @return int
183
     */
184
    public function getTimeSlots()
185
    {
186
        return $this->timeSlots;
187
    }
188
189
    /**
190
     * @param int $timeSlots
191
     * @return Solver
192
     * @throws OptimiseException
193
     */
194 4
    public function setTimeSlots($timeSlots)
195
    {
196 4
        if(!is_int($timeSlots) || $timeSlots <=0)
197 4
            throw new OptimiseException('$timeSlots is not integer or it is not >0');
198
199 4
        $this->timeSlots = $timeSlots;
200 4
        return $this;
201
    }
202
203
    /**
204
     * @return int
205
     */
206
    public function getMaxTimeSlots()
207
    {
208
        return $this->maxTimeSlots;
209
    }
210
211
    /**
212
     * @param int $maxTimeSlots
213
     * @return Solver
214
     * @throws OptimiseException
215
     */
216 4
    public function setMaxTimeSlots($maxTimeSlots)
217
    {
218 4
        if(!is_int($maxTimeSlots) || $maxTimeSlots <=0)
219 4
            throw new OptimiseException('$maxTimeSlots is not integer or it is not >0');
220
221 4
        $this->maxTimeSlots = $maxTimeSlots;
222 4
        return $this;
223
    }
224
225
    /**
226
     * @return string[]
227
     */
228
    public function getMeetingsAvailability()
229
    {
230
        return $this->meetingsAvailability;
231
    }
232
233
    /**
234
     * @param string[] $meetingsAvailability
235
     * @return Solver
236
     * @throws OptimiseException
237
     */
238 4
    public function setMeetingsAvailability($meetingsAvailability)
239
    {
240 4
        $meetings = array_keys($meetingsAvailability);
241 4
        if(array_diff($meetings, $this->meetings))
242 4
            throw new OptimiseException('meetings different from meetings set');
243 4
        foreach($meetingsAvailability as $key=>$meetingsAvailabilityS) {
244 4
            $timeSlots = array_keys($meetingsAvailabilityS);
245 4
            if(count($timeSlots) != $this->timeSlots)
246 4
                throw new OptimiseException('timeSlots different from timeSlots set');
247 4
            $meetingsAvailability[$key] = self::arrayPad($meetingsAvailabilityS, $this->timeSlots + $this->maxTimeSlots, 0);
0 ignored issues
show
Bug introduced by
It seems like $meetingsAvailabilityS defined by $meetingsAvailabilityS on line 243 can also be of type string; however, plunner\Console\Commands...mise\Solver::arrayPad() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
248 4
        }
249
250 4
        $this->meetingsAvailability = $meetingsAvailability;
0 ignored issues
show
Documentation Bug introduced by
It seems like $meetingsAvailability of type array<integer,string|array> is incompatible with the declared type array<integer,string> of property $meetingsAvailability.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
251 4
        return $this;
252
    }
253
254
    /**
255
     * @return string[]
256
     */
257
    public function getMeetingsDuration()
258
    {
259
        return $this->meetingsDuration;
260
    }
261
262
    /**
263
     * @param string[] $meetingsDuration
264
     * @return Solver
265
     * @throws OptimiseException
266
     */
267 4
    public function setMeetingsDuration($meetingsDuration)
268
    {
269 4
        $meetings = array_keys($meetingsDuration);
270 4
        if(array_diff($meetings, $this->meetings)) {
271
            print "";
272
            throw new OptimiseException('meetings different from meetings set');
273
        }
274 4
        foreach($meetingsDuration as $duration) {
275 4
            $duration = (int) $duration; //TODO fix this (fix for optimise)
276 4
            if(!is_int($duration) || $duration <=0)
277 4
                throw new OptimiseException('duration is not integer or it is not >0');
278 4
        }
279
280 4
        $this->meetingsDuration = $meetingsDuration;
281 4
        return $this;
282
    }
283
284
    /**
285
     * @return string[]
286
     */
287
    public function getUsersAvailability()
288
    {
289
        return $this->usersAvailability;
290
    }
291
292
    /**
293
     * @param string[] $usersAvailability
294
     * @return Solver
295
     * @throws OptimiseException
296
     */
297 4
    public function setUsersAvailability($usersAvailability)
298
    {
299 4
        $users = array_keys($usersAvailability);
300 4
        if(array_diff($users, $this->users))
301 4
            throw new OptimiseException('users different from users set');
302 4
        foreach($usersAvailability as $key=>$usersAvailabilityS) {
303 4
            $timeSlots = array_keys($usersAvailabilityS);
304 4
            if(count($timeSlots) != $this->timeSlots)
305 4
                throw new OptimiseException('timeSlots different from timeSlots set');
306
307 4
            $usersAvailability[$key] = self::arrayPad($usersAvailabilityS, $this->timeSlots + $this->maxTimeSlots, 0);
0 ignored issues
show
Bug introduced by
It seems like $usersAvailabilityS defined by $usersAvailabilityS on line 302 can also be of type string; however, plunner\Console\Commands...mise\Solver::arrayPad() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
308 4
        }
309
310 4
        $this->usersAvailability = $usersAvailability;
0 ignored issues
show
Documentation Bug introduced by
It seems like $usersAvailability of type array<integer,string|array> is incompatible with the declared type array<integer,string> of property $usersAvailability.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
311 4
        return $this;
312
    }
313
314
    /**
315
     * @return string[]
316
     */
317
    public function getUsersMeetings()
318
    {
319
        return $this->usersMeetings;
320
    }
321
322
    /**
323
     * @param string[] $usersMeetings
324
     * @return Solver
325
     * @throws OptimiseException
326
     */
327 4
    public function setUsersMeetings($usersMeetings)
328
    {
329 4
        $users = array_keys($usersMeetings);
330 4
        if(array_diff($users, $this->users))
331 4
            throw new OptimiseException('users different from users set');
332 4
        foreach($usersMeetings as $usersMeetingsS) {
333 4
            $meetings = array_keys($usersMeetingsS);
334 4
            if(array_diff($meetings, $this->meetings))
335 4
                throw new OptimiseException('meetings different from meetings set');
336 4
        }
337
338 4
        $this->usersMeetings = $usersMeetings;
339 4
        return $this;
340
    }
341
342
    /**
343
     * @throws OptimiseException
344
     */
345 4
    private function writeUsers()
346
    {
347 4
        self::writeCSVArrayNoKey($this->path->getUsersPath(), $this->users);
348 4
    }
349
350
    /**
351
     * @throws OptimiseException
352
     */
353 4
    private function writeMeetings()
354
    {
355 4
        self::writeCSVArrayNoKey($this->path->getMeetingsPath(), $this->meetings);
356 4
    }
357
358
    /**
359
     * @throws OptimiseException
360
     */
361 4
    private function writeMeetingsDuration()
362
    {
363 4
        self::writeCSVArray($this->path->getMeetingsDurationPath(), $this->meetingsDuration, 'MeetingsDuration');
364 4
    }
365
366
    /**
367
     * @throws OptimiseException
368
     */
369 4
    private function writeMeetingsAvailability()
370
    {
371 4
        self::writeCSVMatrix($this->path->getMeetingsAvailabilityPath(), $this->meetingsAvailability, 'MeetingsAvailability');
372 4
    }
373
374
    /**
375
     * @throws OptimiseException
376
     */
377 4
    private function writeUsersAvailability()
378
    {
379 4
        self::writeCSVMatrix($this->path->getUsersAvailabilityPath(), $this->usersAvailability, 'UsersAvailability');
380 4
    }
381
382
    /**
383
     * @throws OptimiseException
384
     */
385 4
    private function writeUsersMeetings()
386
    {
387 4
        self::writeCSVMatrix($this->path->getUsersMeetingsPath(), $this->usersMeetings, 'UsersMeetings');
388 4
    }
389
390
    /**
391
     * @param string $file
392
     * @param array $data
393
     * @throws OptimiseException
394
     */
395 4
    static private function writeCSVArrayNoKey($file, $data)
396
    {
397
        $f = function ($fp, $data){
398 4
            foreach ($data as $field) {
399 4
                fputcsv($fp, [$field]);
400 4
            }
401 4
        };
402
403 4
        self::writeCSV($file, $data, ['i'], $f);
404 4
    }
405
406
    /**
407
     * @param string $file
408
     * @param array $data
409
     * @param string $name
410
     * @throws OptimiseException
411
     */
412 4
    static private function writeCSVArray($file, $data, $name)
413
    {
414
        $f = function ($fp, $data){
415 4
            foreach ($data as $key=>$field) {
416 4
                fputcsv($fp, [$key, $field]);
417 4
            }
418 4
        };
419
420 4
        self::writeCSV($file, $data, ['i', $name], $f);
421 4
    }
422
423
    /**
424
     * @param string $file
425
     * @param array $data
426
     * @param string $name
427
     * @throws OptimiseException
428
     */
429 4
    static private function writeCSVMatrix($file, $data, $name)
430
    {
431
        $f = function ($fp, $data){
432 4
            foreach ($data as $key=>$field) {
433 4
                foreach ($field as $key2=>$field2)
434 4
                    fputcsv($fp, [$key, $key2, $field2]);
435 4
            }
436 4
        };
437
438 4
        self::writeCSV($file, $data, ['i', 'j', $name], $f);
439 4
    }
440
441
    /**
442
     * @param string $file
443
     * @param array $data
444
     * @param array $heading
445
     * @param \Closure $writer
446
     * @throws OptimiseException
447
     */
448 4
    static private function writeCSV($file, $data, $heading, \Closure $writer)
449
    {
450 4
        $fp = @fopen($file, 'w');
451 4
        if(!$fp)
452 4
            throw new OptimiseException('problem during creation of a file');
453
454 4
        fputcsv($fp, $heading);
455
456 4
        $writer($fp, $data);
457
458
        //fputcsv($fp, []); //empty line
459
460 4
        fclose($fp);
461 4
    }
462
463
    /**
464
     * @return Solver
465
     * @throws OptimiseException
466
     */
467 4
    public function solve()
468
    {
469 4
        $this->writeData();
470 4
        $this->writeModelFile();
471
        $event = $this->schedule->exec('glpsol --math '.$this->path->getModelPath())->sendOutputTo($this->path->getOutputPath())->after(function () { }); //this just to execute in foreground
472 4
        if($event->isDue($this->laravel))
473 4
            $event->run($this->laravel);
474
        //TODO catch glpsol errors
475 4
        return $this;
476
    }
477
478
    /**
479
     * @throws OptimiseException
480
     */
481 4
    private function writeModelFile()
482
    {
483 4
        $strReplaceS = array('{USERS_PATH}', '{MEETINGS_PATH}', '{USER_AVAILABILITY_PATH}', '{MEETINGS_AVAILABILITY_PATH}', '{USER_MEETINGS_PATH}', '{MEETINGS_DURATION_PATH}', '{TIME_SLOTS}', '{MAX_TIME_SLOTS}', '{X_OUT_PATH}', '{Y_OUT_PATH}');
484 4
        $strReplaceR = array($this->path->getUsersPath(), $this->path->getMeetingsPath(), $this->path->getUsersAvailabilityPath(), $this->path->getMeetingsAvailabilityPath(), $this->path->getUsersMeetingsPath(), $this->path->getMeetingsDurationPath(), $this->timeSlots, $this->maxTimeSlots, $this->path->getXPath(), $this->path->getYPath());
485 4
        $f = @fopen($this->path->getModelPath(), "w");
486 4
        if(!$f)
487 4
            throw new OptimiseException('problem during creation of a file');
488 4
        fwrite($f, str_replace($strReplaceS, $strReplaceR, file_get_contents(__DIR__ . "/model.stub")));
489 4
        fclose($f);
490 4
    }
491
492
    /**
493
     * @return array
494
     * @throws OptimiseException
495
     */
496 4
    public function getXResults()
497
    {
498 4
        return self::readCSVFile($this->path->getXPath());
499
    }
500
501
    /**
502
     * @return array
503
     * @throws OptimiseException
504
     */
505 4
    public function getYResults()
506
    {
507 4
        return self::readCSVFile($this->path->getYPath());
508
    }
509
510
    /**
511
     * @return string
512
     * @throws OptimiseException
513
     */
514
    public function getOutput()
515
    {
516
        if(!($data = file_get_contents($this->path->getOutputPath())))
517
            throw new OptimiseException('problems during reading the file');
518
        return $data;
519
    }
520
521
    /**
522
     * @param string $file
523
     * @return array
524
     * @throws OptimiseException
525
     */
526 4
    static private function readCSVFile($file)
527
    {
528 4
        if(!file_exists($file) || !filesize($file))
529 4
            throw new OptimiseException('no results file');
530
531 4
        $handle = @fopen($file,"r");
532 4
        if(!$handle)
533 4
            throw new OptimiseException('problems during reading the file');
534
535 4
        $ret = [];
536 4
        fgetcsv($handle); //skip head
537 4
        while (($data = fgetcsv($handle)) !== FALSE) {
538 4
            if(count($data) != 3) {
539
                fclose($handle);
540
                throw new OptimiseException('problems during parsing the file');
541
            }
542
543 4
            $ret[$data[0]][$data[1]] = $data[2];
544 4
        }
545
546 4
        fclose($handle);
547
548 4
        return $ret;
549
    }
550
551
    /**
552
     * @throws OptimiseException
553
     */
554 4
    private function writeData()
555
    {
556 4
        $this->checkData();
557 4
        $this->writeUsers();
558 4
        $this->writeMeetings();
559 4
        $this->writeMeetingsDuration();
560 4
        $this->writeMeetingsAvailability();
561 4
        $this->writeUsersAvailability();
562 4
        $this->writeUsersMeetings();
563 4
    }
564
565
    /**
566
     * @throws OptimiseException
567
     */
568 4
    private function checkData()
569
    {
570 4
        $this->checkArrayProprieties(['users', 'meetings', 'meetingsAvailability', 'meetingsDuration', 'usersAvailability', 'usersMeetings']);
571 4
        $this->checkIntProprieties(['timeSlots', 'maxTimeSlots']);
572 4
    }
573
574
    /**
575
     * @param $proprieties
576
     * @throws OptimiseException
577
     */
578 4
    private function checkArrayProprieties($proprieties)
579
    {
580 4
        foreach($proprieties as $propriety)
581 4
            if(count($this->$propriety)==0)
582 4
                throw new OptimiseException($propriety.' is not set correctly');
583 4
    }
584
585
    /**
586
     * @param $proprieties
587
     * @throws OptimiseException
588
     */
589 4
    private function checkIntProprieties($proprieties)
590
    {
591 4
        foreach($proprieties as $propriety)
592 4
            if(!is_int($this->$propriety) || $this->$propriety <= 0)
593 4
                throw new OptimiseException($propriety.' is not set correctly');
594 4
    }
595
596
    /**
597
     * implementation of arraypad that doesn't change original keys<br/>
598
     * <strong>CAUTION: Only positive $len</strong>
599
     * @param array $array
600
     * @return array
601
     */
602 4
    static private function arrayPad(array $array, $len, $pad)
603
    {
604 4
        $len = $len - count($array);
605 4
        for($i = 0; $i<$len; $i++)
606 4
            $array[] = $pad;
607 4
        return $array;
608
    }
609
}