Completed
Push — master ( 468aac...e5acfe )
by claudio
06:32
created

Solver::checkArrayProprieties()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 6
ccs 5
cts 5
cp 1
rs 9.4286
cc 3
eloc 4
nc 3
nop 1
crap 3
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 15
    public function __construct(Schedule $schedule, Application $laravel)
95
    {
96 15
        self::checkGlpsol();
97 15
        $this->path = Path::createPath();
98 15
        $this->schedule = $schedule;
99 15
        $this->laravel = $laravel;
100 15
    }
101
102
    /**
103
     * @throws OptimiseException
104
     */
105 15
    static private function checkGlpsol()
106
    {
107 15
        if (!(`which glpsol`))
108 10
            throw new OptimiseException('glpsol is not installed');
109 15
    }
110
111
    /**
112
     * @throws OptimiseException
113
     */
114 8
    public function __destruct()
115
    {
116 8
        $this->path = null; //call the path destruct
117 8
    }
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 6
    public function getUsers()
149
    {
150 6
        return $this->users;
151
    }
152
153
    /**
154
     * @param string[] $users
155
     * @return Solver
156
     */
157 12
    public function setUsers($users)
158
    {
159 12
        $this->users = $users;
160 12
        return $this;
161
    }
162
163
    /**
164
     * @return string[]
165
     */
166 6
    public function getMeetings()
167
    {
168 6
        return $this->meetings;
169
    }
170
171
    /**
172
     * @param string[] $meetings
173
     * @return Solver
174
     */
175 9
    public function setMeetings($meetings)
176
    {
177 9
        $this->meetings = $meetings;
178 9
        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 15
    public function setTimeSlots($timeSlots)
195
    {
196 15
        if (!is_int($timeSlots) || $timeSlots <= 0)
197 10
            throw new OptimiseException('$timeSlots is not integer or it is not >0');
198
199 15
        $this->timeSlots = $timeSlots;
200 15
        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 15
    public function setMaxTimeSlots($maxTimeSlots)
217
    {
218 15
        if (!is_int($maxTimeSlots) || $maxTimeSlots <= 0)
219 10
            throw new OptimiseException('$maxTimeSlots is not integer or it is not >0');
220
221 15
        $this->maxTimeSlots = $maxTimeSlots;
222 15
        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 9
    public function setMeetingsAvailability($meetingsAvailability)
239
    {
240 9
        $meetings = array_keys($meetingsAvailability);
241 9
        if (array_diff($meetings, $this->meetings))
242 6
            throw new OptimiseException('meetings different from meetings set');
243 9
        foreach ($meetingsAvailability as $key => $meetingsAvailabilityS) {
244 9
            $timeSlots = array_keys($meetingsAvailabilityS);//TODO this is useless, we can use directly $usersAvailabilityS
245 9
            if (count($timeSlots) != $this->timeSlots)
246 6
                throw new OptimiseException('timeSlots different from timeSlots set');
247 9
            $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 6
        }
249
250 9
        $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 9
        return $this;
252
    }
253
254
    /**
255
     * implementation of arraypad that doesn't change original keys<br/>
256
     * <strong>CAUTION: Only positive $len</strong>
257
     * @param array $array
258
     * @return array
259
     */
260 9
    static private function arrayPad(array $array, $len, $pad)
261
    {
262 9
        $len = $len - count($array);
263 9
        for ($i = 0; $i < $len; $i++)
264 9
            $array[] = $pad;
265 9
        return $array;
266
    }
267
268
    /**
269
     * @return string[]
270
     */
271
    public function getMeetingsDuration()
272
    {
273
        return $this->meetingsDuration;
274
    }
275
276
    /**
277
     * @param string[] $meetingsDuration
278
     * @return Solver
279
     * @throws OptimiseException
280
     */
281 9
    public function setMeetingsDuration($meetingsDuration)
282
    {
283 9
        $meetings = array_keys($meetingsDuration);
284 9
        if (array_diff($meetings, $this->meetings)) {
285
            throw new OptimiseException('meetings different from meetings set');
286
        }
287 9
        foreach ($meetingsDuration as $duration) {
288 9
            $duration = (int)$duration; //TODO fix this (fix for optimise)
289 9
            if (!is_int($duration) || $duration <= 0)
290 9
                throw new OptimiseException('duration is not integer or it is not >0');
291 6
        }
292
293 9
        $this->meetingsDuration = $meetingsDuration;
294 9
        return $this;
295
    }
296
297
    /**
298
     * @return string[]
299
     */
300
    public function getUsersAvailability()
301
    {
302
        return $this->usersAvailability;
303
    }
304
305
    /**
306
     * @param string[] $usersAvailability
307
     * @return Solver
308
     * @throws OptimiseException
309
     */
310 9
    public function setUsersAvailability($usersAvailability)
311
    {
312 9
        $users = array_keys($usersAvailability);
313 9
        if (array_diff($users, $this->users))
314 6
            throw new OptimiseException('users different from users set');
315 9
        foreach ($usersAvailability as $key => $usersAvailabilityS) {
316 9
            $timeSlots = array_keys($usersAvailabilityS);//TODO this is useless, we can use directly $usersAvailabilityS
317 9
            if (count($timeSlots) != $this->timeSlots)
318 6
                throw new OptimiseException('timeSlots different from timeSlots set');
319
320 9
            $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 315 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...
321 6
        }
322
323 9
        $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...
324 9
        return $this;
325
    }
326
327
    /**
328
     * @return string[]
329
     */
330
    public function getUsersMeetings()
331
    {
332
        return $this->usersMeetings;
333
    }
334
335
    /**
336
     * @param string[] $usersMeetings
337
     * @return Solver
338
     * @throws OptimiseException
339
     */
340 9
    public function setUsersMeetings($usersMeetings)
341
    {
342 9
        $users = array_keys($usersMeetings);
343 9
        if (array_diff($users, $this->users))
344 6
            throw new OptimiseException('users different from users set');
345 9
        foreach ($usersMeetings as $usersMeetingsS) {
346 9
            $meetings = array_keys($usersMeetingsS);
347 9
            if (array_diff($meetings, $this->meetings))
348 9
                throw new OptimiseException('meetings different from meetings set');
349 6
        }
350
351 9
        $this->usersMeetings = $usersMeetings;
352 9
        return $this;
353
    }
354
355
    /**
356
     * @return Solver
357
     * @throws OptimiseException
358
     */
359 9
    public function solve()
360
    {
361 9
        $this->writeData();
362 9
        $this->writeModelFile();
363
        $event = $this->schedule->exec('glpsol --math ' . $this->path->getModelPath())->sendOutputTo($this->path->getOutputPath())->after(function () {
364 9
        }); //this just to execute in foreground
365 9
        if ($event->isDue($this->laravel))
366 9
            $event->run($this->laravel);
367
        //TODO catch glpsol errors
368 9
        return $this;
369
    }
370
371
    /**
372
     * @throws OptimiseException
373
     */
374 9
    private function writeData()
375
    {
376 9
        $this->checkData();
377 9
        $this->writeUsers();
378 9
        $this->writeMeetings();
379 9
        $this->writeMeetingsDuration();
380 9
        $this->writeMeetingsAvailability();
381 9
        $this->writeUsersAvailability();
382 9
        $this->writeUsersMeetings();
383 9
    }
384
385
    /**
386
     * @throws OptimiseException
387
     */
388 9
    private function checkData()
389
    {
390 9
        $this->checkArrayProprieties(['users', 'meetings', 'meetingsAvailability', 'meetingsDuration', 'usersAvailability', 'usersMeetings']);
391 9
        $this->checkIntProprieties(['timeSlots', 'maxTimeSlots']);
392 9
    }
393
394
    /**
395
     * @param $proprieties
396
     * @throws OptimiseException
397
     */
398 9
    private function checkArrayProprieties($proprieties)
399
    {
400 9
        foreach ($proprieties as $propriety)
401 9
            if (count($this->$propriety) == 0)
402 9
                throw new OptimiseException($propriety . ' property is not set correctly');
403 9
    }
404
405
    /**
406
     * @param $proprieties
407
     * @throws OptimiseException
408
     */
409 9
    private function checkIntProprieties($proprieties)
410
    {
411 9
        foreach ($proprieties as $propriety)
412 9
            if (!is_int($this->$propriety) || $this->$propriety <= 0)
413 9
                throw new OptimiseException($propriety . ' property is not set correctly');
414 9
    }
415
416
    /**
417
     * @throws OptimiseException
418
     */
419 9
    private function writeUsers()
420
    {
421 9
        self::writeCSVArrayNoKey($this->path->getUsersPath(), $this->users);
422 9
    }
423
424
    /**
425
     * @param string $file
426
     * @param array $data
427
     * @throws OptimiseException
428
     */
429 9
    static private function writeCSVArrayNoKey($file, $data)
430
    {
431
        $f = function ($fp, $data) {
432 9
            foreach ($data as $field) {
433 9
                fputcsv($fp, [$field]);
434 6
            }
435 9
        };
436
437 9
        self::writeCSV($file, $data, ['i'], $f);
438 9
    }
439
440
    /**
441
     * @param string $file
442
     * @param array $data
443
     * @param array $heading
444
     * @param \Closure $writer
445
     * @throws OptimiseException
446
     */
447 9
    static private function writeCSV($file, $data, $heading, \Closure $writer)
448
    {
449 9
        $fp = @fopen($file, 'w');
450 9
        if (!$fp)
451 6
            throw new OptimiseException('problem during creation of a file');
452
453 9
        fputcsv($fp, $heading);
454
455 9
        $writer($fp, $data);
456
457
        //fputcsv($fp, []); //empty line
458
459 9
        fclose($fp);
460 9
    }
461
462
    /**
463
     * @throws OptimiseException
464
     */
465 9
    private function writeMeetings()
466
    {
467 9
        self::writeCSVArrayNoKey($this->path->getMeetingsPath(), $this->meetings);
468 9
    }
469
470
    /**
471
     * @throws OptimiseException
472
     */
473 9
    private function writeMeetingsDuration()
474
    {
475 9
        self::writeCSVArray($this->path->getMeetingsDurationPath(), $this->meetingsDuration, 'MeetingsDuration');
476 9
    }
477
478
    /**
479
     * @param string $file
480
     * @param array $data
481
     * @param string $name
482
     * @throws OptimiseException
483
     */
484 9
    static private function writeCSVArray($file, $data, $name)
485
    {
486
        $f = function ($fp, $data) {
487 9
            foreach ($data as $key => $field) {
488 9
                fputcsv($fp, [$key, $field]);
489 6
            }
490 9
        };
491
492 9
        self::writeCSV($file, $data, ['i', $name], $f);
493 9
    }
494
495
    /**
496
     * @throws OptimiseException
497
     */
498 9
    private function writeMeetingsAvailability()
499
    {
500 9
        self::writeCSVMatrix($this->path->getMeetingsAvailabilityPath(), $this->meetingsAvailability, 'MeetingsAvailability');
501 9
    }
502
503
    /**
504
     * @param string $file
505
     * @param array $data
506
     * @param string $name
507
     * @throws OptimiseException
508
     */
509
    static private function writeCSVMatrix($file, $data, $name)
510
    {
511 9
        $f = function ($fp, $data) {
512 9
            foreach ($data as $key => $field) {
513 9
                foreach ($field as $key2 => $field2)
514 9
                    fputcsv($fp, [$key, $key2, $field2]);
515 6
            }
516 9
        };
517
518 9
        self::writeCSV($file, $data, ['i', 'j', $name], $f);
519 9
    }
520
521
    /**
522
     * @throws OptimiseException
523
     */
524 9
    private function writeUsersAvailability()
525
    {
526 9
        self::writeCSVMatrix($this->path->getUsersAvailabilityPath(), $this->usersAvailability, 'UsersAvailability');
527 9
    }
528
529
    /**
530
     * @throws OptimiseException
531
     */
532 9
    private function writeUsersMeetings()
533
    {
534 9
        self::writeCSVMatrix($this->path->getUsersMeetingsPath(), $this->usersMeetings, 'UsersMeetings');
535 9
    }
536
537
    /**
538
     * @throws OptimiseException
539
     */
540 9
    private function writeModelFile()
541
    {
542 9
        $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}');
543 9
        $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());
544 9
        $f = @fopen($this->path->getModelPath(), "w");
545 9
        if (!$f)
546 6
            throw new OptimiseException('problem during creation of a file');
547 9
        fwrite($f, str_replace($strReplaceS, $strReplaceR, file_get_contents(__DIR__ . "/model.stub")));
548 9
        fclose($f);
549 9
    }
550
551
    /**
552
     * @return array
553
     * @throws OptimiseException
554
     */
555 9
    public function getXResults()
556
    {
557 9
        return self::readCSVFile($this->path->getXPath());
558
    }
559
560
    /**
561
     * @param string $file
562
     * @return array
563
     * @throws OptimiseException
564
     */
565 9
    static private function readCSVFile($file)
566
    {
567 9
        if (!file_exists($file) || !filesize($file))
568 6
            throw new OptimiseException('no results file');
569
570 9
        $handle = @fopen($file, "r");
571 9
        if (!$handle)
572 6
            throw new OptimiseException('problems during reading the file');
573
574 9
        $ret = [];
575 9
        fgetcsv($handle); //skip head
576 9
        while (($data = fgetcsv($handle)) !== FALSE) {
577 9
            if (count($data) != 3) {
578
                fclose($handle);
579
                throw new OptimiseException('problems during parsing the file');
580
            }
581
582 9
            $ret[$data[0]][$data[1]] = $data[2];
583 6
        }
584
585 9
        fclose($handle);
586
587 9
        return $ret;
588
    }
589
590
    /**
591
     * @return array
592
     * @throws OptimiseException
593
     */
594 9
    public function getYResults()
595
    {
596 9
        return self::readCSVFile($this->path->getYPath());
597
    }
598
599
    /**
600
     * @return string
601
     * @throws OptimiseException
602
     */
603
    public function getOutput()
604
    {
605
        if (!($data = file_get_contents($this->path->getOutputPath())))
606
            throw new OptimiseException('problems during reading the file');
607
        return $data;
608
    }
609
}