mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-11 17:58:41 +00:00
Compare commits
1949 Commits
v1.0.0-alp
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| cb4c61fb5b | |||
| a480f4712f | |||
| 250f64556d | |||
|
|
326e2172ec | ||
|
|
af9a7e1f29 | ||
|
|
efd5580a75 | ||
|
|
4dff3d3289 | ||
|
|
126fca5574 | ||
|
|
5230892673 | ||
|
|
ca57051cd7 | ||
|
|
370e4fa420 | ||
|
|
f7fe30e545 | ||
| 167c7f08e7 | |||
|
|
982fab8018 | ||
|
|
189d37580d | ||
|
|
121e6b4486 | ||
|
|
1d49ceb5a0 | ||
|
|
f991f15889 | ||
|
|
1c2ecda287 | ||
|
|
ab3e8e0939 | ||
|
|
61f44bc044 | ||
|
|
b2cd928809 | ||
|
|
9ca0be2825 | ||
|
|
27ad14b4c5 | ||
|
|
d6ddf6cbe9 | ||
|
|
a1b591d141 | ||
| 7bb5c3eb83 | |||
|
|
8c2ea75430 | ||
|
|
384b784995 | ||
|
|
279bd8a3bf | ||
|
|
0bf99750b8 | ||
|
|
44098e33d2 | ||
|
|
5876d88901 | ||
|
|
7b76d797b8 | ||
|
|
a2e7ae6a25 | ||
|
|
cc243cffd7 | ||
|
|
48c5164e12 | ||
|
|
642b74ad22 | ||
|
|
2a41801b77 | ||
|
|
006a611a07 | ||
| 381bf25f13 | |||
|
|
405638417e | ||
|
|
e7b708185e | ||
|
|
84fa78359b | ||
|
|
01649f0489 | ||
|
|
84d73bf821 | ||
|
|
f14f65ffd3 | ||
|
|
8f735ef121 | ||
|
|
b92e1f08e7 | ||
|
|
67886a9dc8 | ||
|
|
4e1630a124 | ||
|
|
f1a64bdc64 | ||
|
|
560f8a2796 | ||
|
|
ea0b033e1c | ||
|
|
b5944503d8 | ||
|
|
b447c0772c | ||
|
|
96f9db5395 | ||
|
|
047d96a776 | ||
|
|
5433e64d8e | ||
|
|
d4aa5b1bc5 | ||
|
|
f3b0ff3756 | ||
|
|
ce150dea48 | ||
|
|
03723410dc | ||
|
|
3bed5e3cc0 | ||
|
|
bf235e34ee | ||
|
|
b7fa018718 | ||
|
|
f3f3f2846b | ||
|
|
5c38b8b250 | ||
|
|
0c9921b737 | ||
|
|
6448930755 | ||
|
|
bbb9d06f29 | ||
|
|
36b7143829 | ||
|
|
7b90d4eb54 | ||
|
|
fa3d48bc24 | ||
|
|
171413c108 | ||
|
|
268495a5f3 | ||
|
|
d547401cb1 | ||
|
|
889e71e5ab | ||
|
|
91c59ba329 | ||
|
|
d904e6e0bc | ||
|
|
4645d59b8a | ||
|
|
9018cf5b57 | ||
|
|
1675a82410 | ||
|
|
dd0a71421a | ||
|
|
d22f0a6d18 | ||
|
|
ec49bf2dc1 | ||
|
|
999ec823b3 | ||
|
|
d96a842e81 | ||
|
|
61253e92b4 | ||
|
|
b871ba283c | ||
|
|
1b738dfe5a | ||
|
|
45bab0c041 | ||
|
|
5f8004da25 | ||
|
|
b7c9453621 | ||
|
|
479c48b713 | ||
|
|
f79ba2f67e | ||
|
|
d2d0c6bfcf | ||
|
|
bf9d528076 | ||
|
|
ab7b467c1d | ||
|
|
660e2dbbfd | ||
|
|
8c5486bed4 | ||
|
|
60fa60a91a | ||
|
|
310a716769 | ||
|
|
ab5230b8bd | ||
|
|
58120ac97d | ||
|
|
b4384f965a | ||
|
|
484963099d | ||
|
|
31a77c08e5 | ||
|
|
bbbf96ad8e | ||
|
|
6e461461e0 | ||
|
|
5ba31111bc | ||
|
|
95eb37853e | ||
|
|
cd36282b4f | ||
|
|
17603e2d05 | ||
|
|
941bdac44e | ||
|
|
d8e0d40c6a | ||
|
|
630a38b897 | ||
|
|
4bed0db195 | ||
|
|
1e197375c5 | ||
|
|
f435905146 | ||
|
|
90b807093b | ||
|
|
1f9f97f8d9 | ||
|
|
c52b6c8863 | ||
|
|
8f0f105b8a | ||
|
|
3656d6df97 | ||
|
|
8c9e6929d0 | ||
|
|
8b3daf3c8d | ||
|
|
7a086fea93 | ||
|
|
2cbbe69bac | ||
|
|
a4f7d2e527 | ||
|
|
5a1ea1ee49 | ||
|
|
fdcf1645f2 | ||
|
|
8fd2cca24b | ||
|
|
9c02749567 | ||
|
|
772453e753 | ||
|
|
8a8ee4ed90 | ||
|
|
60ccfdb242 | ||
|
|
9d2b2e2e06 | ||
|
|
65264e33ae | ||
|
|
c224209c28 | ||
|
|
9b71428f88 | ||
|
|
b4af5e237c | ||
|
|
5e6ccb0b36 | ||
|
|
663cf6b11e | ||
|
|
4a208b8cd0 | ||
|
|
0d8a2fc59d | ||
|
|
9c86108a0f | ||
|
|
dcc60246df | ||
|
|
2b6e7b008d | ||
|
|
bd29e83c06 | ||
|
|
4807dcf5c2 | ||
|
|
e997408bb6 | ||
|
|
0b5d51f9b8 | ||
|
|
b9aec88a4b | ||
|
|
513fa7bf8b | ||
|
|
ae6391ecf8 | ||
|
|
624d6b517e | ||
|
|
2655c816a9 | ||
|
|
567ab3bfcd | ||
|
|
99211861eb | ||
|
|
eff9f243d9 | ||
|
|
007fc6dfe8 | ||
|
|
cd9a5cdc11 | ||
|
|
e873f909fd | ||
|
|
febcc20001 | ||
| 2e32c8a7cc | |||
|
|
6dcb540248 | ||
| a3f4d61b6e | |||
|
|
d42fee56af | ||
|
|
39418dbe23 | ||
|
|
e51b7ba93c | ||
|
|
cdea41f714 | ||
|
|
be8ae797db | ||
|
|
42ca4d6690 | ||
|
|
69f64f331b | ||
|
|
bdf238a522 | ||
|
|
7505eadee1 | ||
|
|
39a55a590b | ||
|
|
48de45149b | ||
|
|
3d8d0abaf6 | ||
| 695984e4fe | |||
| 9699842a7e | |||
|
|
d56488a58a | ||
|
|
83277e9b6f | ||
|
|
8efe04fc6b | ||
|
|
135f141c06 | ||
|
|
d9167a9d06 | ||
|
|
fa68acc4be | ||
|
|
13bf197ac7 | ||
|
|
5d66591a55 | ||
|
|
b969bc927c | ||
|
|
abff265761 | ||
|
|
be8e1c8526 | ||
|
|
b51dfbd9ca | ||
|
|
ac5273bbc5 | ||
|
|
d272df38d4 | ||
|
|
ccca21c0e3 | ||
|
|
e07798e3b6 | ||
|
|
ee9391fd97 | ||
|
|
f445678207 | ||
|
|
1ca15ebbfa | ||
|
|
513a1df1dc | ||
|
|
4327536119 | ||
|
|
9cef4cddaf | ||
|
|
fb35f86c5c | ||
|
|
fe9f2f3954 | ||
|
|
3200e2abed | ||
|
|
5f551fe5f3 | ||
|
|
f498435a41 | ||
|
|
6c84a1e6af | ||
|
|
250edfdb63 | ||
|
|
40acf30ad9 | ||
|
|
913f044dc2 | ||
|
|
a0de10c939 | ||
|
|
2cbab79465 | ||
|
|
213ed899a9 | ||
|
|
6e25ad9313 | ||
|
|
8cec0e3dfe | ||
|
|
33cf7b521e | ||
|
|
6840f9b9e7 | ||
|
|
2dbaeba97e | ||
|
|
3f3ec8b697 | ||
|
|
9b1104037f | ||
|
|
397696c122 | ||
|
|
ea2ba6aa7d | ||
|
|
4611258418 | ||
|
|
f11873cb4c | ||
|
|
72ec944756 | ||
|
|
3fa5184167 | ||
|
|
23cf5e4a48 | ||
|
|
28a6ec5951 | ||
|
|
80131384cb | ||
|
|
f83e60a592 | ||
|
|
5de349ba68 | ||
|
|
377dc8a821 | ||
|
|
788755dd60 | ||
|
|
f45fb503fb | ||
|
|
c10e65bcf5 | ||
|
|
e7ceebfccb | ||
|
|
85bed9ec2f | ||
|
|
75dc3bc7d3 | ||
|
|
3264ca7ccb | ||
|
|
6c0651afa6 | ||
|
|
9e2efeff7d | ||
|
|
ac3c43e17d | ||
|
|
c224fe74d5 | ||
|
|
94b293e1df | ||
|
|
ddd1b04b3a | ||
|
|
aecb807555 | ||
|
|
650789ba16 | ||
|
|
662651236e | ||
|
|
9c1c339d37 | ||
|
|
82a14e4704 | ||
|
|
7fea04d08e | ||
|
|
455c9c8069 | ||
|
|
c87d1df047 | ||
|
|
5c9b0e88c3 | ||
|
|
b6b9dd39b9 | ||
|
|
0080e35aac | ||
|
|
dfd81edcc3 | ||
|
|
58de0b90df | ||
|
|
118789e0f8 | ||
|
|
acf4ea21f3 | ||
|
|
05d913b09a | ||
|
|
b69c9b708c | ||
|
|
37db31e85f | ||
|
|
5d2ff0aa47 | ||
|
|
0dec47823e | ||
|
|
ef3849164a | ||
|
|
c2dea0f000 | ||
|
|
e8fbb0bc2c | ||
|
|
173c501b54 | ||
|
|
4f48347c16 | ||
|
|
06471f5f71 | ||
|
|
cf72e2f5ec | ||
|
|
6e0aebb074 | ||
|
|
ad982dda8f | ||
|
|
eddc7bff03 | ||
|
|
7882ce95be | ||
|
|
d78dfa7e74 | ||
|
|
2473992319 | ||
|
|
6be8c1fdc4 | ||
|
|
d5b0b73f2a | ||
|
|
cd00e471b9 | ||
|
|
c88829efd6 | ||
|
|
1e23943085 | ||
|
|
64aec26f95 | ||
|
|
ff53814f43 | ||
|
|
829bef8f01 | ||
|
|
0c612c339d | ||
|
|
6805b24820 | ||
|
|
d1edf04232 | ||
|
|
6b74b8d968 | ||
|
|
0d200da299 | ||
|
|
e579f1ab30 | ||
| 40b794d880 | |||
|
|
9793782e48 | ||
|
|
b9bd5d618f | ||
| dea660ca91 | |||
|
|
0b9a8687e6 | ||
|
|
9078fe6f65 | ||
|
|
5b52a92d38 | ||
|
|
3925cb6be7 | ||
|
|
d8fd9b74d2 | ||
|
|
17756362a1 | ||
|
|
5babc18595 | ||
|
|
af38754915 | ||
|
|
7bf216708b | ||
|
|
c9a03bfff3 | ||
|
|
177d7cf2b5 | ||
|
|
9f319c4e30 | ||
|
|
9a99bea741 | ||
|
|
f5887798c2 | ||
|
|
9843328a14 | ||
|
|
11d2ed8f2e | ||
|
|
0844846f51 | ||
|
|
61eb86ae6f | ||
|
|
2f0e9d1c8c | ||
|
|
3493ac10b1 | ||
|
|
4e4405ade1 | ||
|
|
84cf2e96ce | ||
|
|
e08f689afc | ||
|
|
018b9ce981 | ||
|
|
4469789af4 | ||
|
|
447efca623 | ||
|
|
74e1684ad0 | ||
|
|
c4fc99ed39 | ||
|
|
61c9bacb80 | ||
|
|
ab023cad8b | ||
|
|
11b6f5f471 | ||
|
|
bc1cd635fe | ||
|
|
1a7fe13e66 | ||
|
|
d36e96cb85 | ||
|
|
e91e5fd405 | ||
|
|
83a36c1c66 | ||
|
|
513df8987b | ||
|
|
27ac3f18a6 | ||
|
|
ebbc08fe9b | ||
|
|
acf9a247f0 | ||
|
|
0fd9b3edd7 | ||
|
|
c9c41a7212 | ||
| f802139f33 | |||
| 71389a6796 | |||
|
|
1da071f202 | ||
|
|
f1a2c81aeb | ||
| ebc270c84e | |||
|
|
f88a530b96 | ||
|
|
c2538fed60 | ||
|
|
04cb75cede | ||
| 318028da28 | |||
| e05dbd4156 | |||
| 678f066a41 | |||
| 06f1784418 | |||
|
|
1dd478c461 | ||
|
|
0900e31d45 | ||
|
|
a4be7fd316 | ||
|
|
055d25425e | ||
|
|
3938f56f88 | ||
|
|
f09df7ea16 | ||
|
|
fc2fd1343f | ||
|
|
feb054b8fb | ||
|
|
b3663ac127 | ||
|
|
3e3be15c13 | ||
|
|
dfb57a37df | ||
|
|
88f2b7a585 | ||
|
|
55c267f01f | ||
|
|
96ac1d9dff | ||
|
|
4d9f4aa241 | ||
|
|
475b52e67c | ||
|
|
fafa6cd3cd | ||
|
|
a9f04acb31 | ||
|
|
1bd56c74ac | ||
|
|
9edbe6cc4b | ||
|
|
c81bae26cf | ||
|
|
3457f16cd7 | ||
|
|
7c01ac51e9 | ||
|
|
c47d3bb6c1 | ||
| 004eb33a4f | |||
| 5b2c9d499b | |||
| 3d68874c2c | |||
| 859a04c791 | |||
| b9cc466bed | |||
| 4d0a6e5c2d | |||
|
|
f9ae1c3c85 | ||
|
|
ec210f8b8e | ||
| a9745c2f1c | |||
| a7493d205b | |||
| bbbeee84e1 | |||
| df4611731f | |||
| 9cb5138201 | |||
| 0bfb0c2fe1 | |||
| de4f390344 | |||
| 65b455f6c0 | |||
| ce8b9988bf | |||
| 8bace3becf | |||
| 6e42fe49a1 | |||
| 778a8f02eb | |||
| 44dc418ca3 | |||
| bf48a9b1bf | |||
| c9e6d6a210 | |||
| 5902fbdf08 | |||
| de1bf517c2 | |||
| 1bc05c79d6 | |||
| de6831cf36 | |||
| 6e52397ab4 | |||
| 4a3410d182 | |||
| 3740f24c39 | |||
| 7889f9ad7c | |||
| 0cf775c8dd | |||
| 092c9b9f29 | |||
| 88938b7fd1 | |||
| 3d26dc8fdd | |||
| a27b433ef4 | |||
| 8d4736256f | |||
| c35eb52a8b | |||
| 321425ea1f | |||
| 476fc943e9 | |||
| af4dca3ee4 | |||
| a125925f29 | |||
| 833886f970 | |||
| cd0456e1be | |||
| d07c15d1e4 | |||
| e24f460fdf | |||
| 5844ad3a72 | |||
| 60810e17a9 | |||
| 01be5c32d2 | |||
| 51779fb92b | |||
| 3fdc684973 | |||
| 82cdc54fb0 | |||
| 7d9a033833 | |||
| 190d5cac11 | |||
| 868ad1d5c4 | |||
| 8c3d4fffa5 | |||
| df7bbbdb66 | |||
| e9618de67d | |||
| 9745efa7d5 | |||
| 2e442acc3d | |||
| 866b5416a4 | |||
| 71fe6219c8 | |||
| ca5f5aa58e | |||
| 92200f82b2 | |||
| 5f7bca33f7 | |||
| ce8c46292c | |||
| 100e62929c | |||
| 34f53c50e3 | |||
| 2c892ade63 | |||
| 34543e5d34 | |||
| eb85b49c40 | |||
| 11f34bcccc | |||
| 72cbfa19db | |||
| 1e900f77a5 | |||
| 4f401bf2a8 | |||
| bc3b9154e9 | |||
| bbad88d226 | |||
| 9851eeb001 | |||
| 2d408874fa | |||
| 8396a6089d | |||
| 6b6b28d922 | |||
| 9d7d48f424 | |||
| 6459e61a63 | |||
| 06c15c4a09 | |||
| 133d7ff102 | |||
| e0c3b4628d | |||
| aab71632c3 | |||
| ed2e2375b2 | |||
| 01615d3df7 | |||
| 064d5f2e85 | |||
| dc09cbb703 | |||
| f1114b2f0a | |||
| 751ce51220 | |||
| 328e9c547e | |||
| 634cc3d505 | |||
| 5cfa5f5a95 | |||
| f3124aafdf | |||
| 45f64a09aa | |||
| 4b03cd91f5 | |||
| 152f2a39ea | |||
| f2bc629fc0 | |||
| eb3449f308 | |||
| 6e6aa16f84 | |||
| c77874873c | |||
| de0d70679b | |||
| 4841e1538b | |||
| 8f161afcce | |||
| d245e8d184 | |||
| 9e60c0e552 | |||
| aa04922214 | |||
| 7a66e48aa5 | |||
| ca42a6df54 | |||
| ee363e07fc | |||
| 445006e9b4 | |||
| d631e4ce62 | |||
| 2612e1f862 | |||
|
|
7077ab29ea | ||
| 7c8469c114 | |||
| f4dcb64af0 | |||
|
|
e204ff0c47 | ||
| 84fb308b3a | |||
| 87d07b3574 | |||
| d09b3db948 | |||
| cd35204f03 | |||
| 8c865bb0aa | |||
| 35d13cd7db | |||
| d4b116aa85 | |||
| 4027a9159f | |||
| edd4693111 | |||
|
|
3e79dc03d9 | ||
| d62475dc69 | |||
|
|
b4f61f760a | ||
| 6bcd0c4bbe | |||
| 26e2e8a876 | |||
| 3e50583065 | |||
|
|
bc8c30052c | ||
| 4da3c35cbf | |||
| d77f090e33 | |||
| 3207946802 | |||
| b2efd91008 | |||
| 8dcc7feaa1 | |||
| 9ae86275ff | |||
| e2b842c29d | |||
| c1fb9a31dc | |||
| 9f0a6233a0 | |||
| a74d2e8a97 | |||
| fb3988e72c | |||
| b1faa50caa | |||
| 97ad384589 | |||
| cab2628062 | |||
| a2f067920c | |||
| e25b309171 | |||
| d63a71a8de | |||
| 143b39a5ee | |||
| c560be94ba | |||
| b034c617fd | |||
| 4cb31cc3ed | |||
| a03fd72dbd | |||
| f92947175c | |||
| 960fdfcfe9 | |||
| c5cd5da5d9 | |||
| df89ee433e | |||
| c20021464c | |||
| a3ee157bae | |||
| 3e609e6041 | |||
| 1d583a03ea | |||
| a7fc3812ae | |||
| 7456a32a1f | |||
| 288e9d62d6 | |||
| 7ed21a1ec0 | |||
| 755ee54f0c | |||
| a86685a04d | |||
| f157d05c00 | |||
| 516a506556 | |||
| d859f287d5 | |||
| 263ea88951 | |||
| 60e9e35892 | |||
| d4308ec916 | |||
| 625559977f | |||
| fea0569020 | |||
| f47d83a664 | |||
| cb2877f45c | |||
| eb9298ec98 | |||
| d5fef266f5 | |||
| bf0dd44985 | |||
| 169b5535d3 | |||
| d20da74b77 | |||
| 1e90a5ba9d | |||
|
|
89ae5950e4 | ||
| aa004569f5 | |||
| b44aad18d1 | |||
| 3c464a1e21 | |||
| be1cdcf1ec | |||
| e740bfbe3e | |||
| 77f099559b | |||
| 44ea04b2c6 | |||
| 162c30fb64 | |||
| faf8423dd7 | |||
| 6cd43859ec | |||
| bbab09b0ea | |||
| 05066c520e | |||
| 868390a7f3 | |||
| 12b8eea76c | |||
| 5e63303b67 | |||
|
|
d46276184a | ||
| 892788ece2 | |||
| 2cfcb24742 | |||
| 9f8d074e84 | |||
| 3b20d0c475 | |||
| def25c433f | |||
| 4a7de293ff | |||
|
|
6e163187f4 | ||
| 2a288de983 | |||
| 8ec4d17ff7 | |||
| 06c7f308e2 | |||
|
|
747d40f1e7 | ||
| a8ebed1d3b | |||
| b53973157d | |||
| 72b23f2928 | |||
| 534b325c24 | |||
| 746b6ffc7a | |||
|
|
17453e225c | ||
| 9d239ebd98 | |||
| 3d7586f385 | |||
|
|
90b24dbc87 | ||
| 67fe4641e4 | |||
|
|
4ce8879933 | ||
| ba7f68f69f | |||
| 6338db6631 | |||
| f43df4c7c2 | |||
|
|
a42f662062 | ||
| fa7fb0f188 | |||
| 894604c406 | |||
| b62b46ad3c | |||
|
|
56a11bf1db | ||
| 0d63731697 | |||
| d209c16831 | |||
| 9d6e431c3b | |||
|
|
0d83f2a22b | ||
| 002112fca4 | |||
| 2cf30d1073 | |||
| 4d0215580c | |||
| f5d41b0d81 | |||
| a4e7957dbb | |||
| 4accb7b716 | |||
|
|
463532ecc9 | ||
| 5fb18505dc | |||
| 79c91d9895 | |||
| d7e89a4a31 | |||
| 0df18d08e1 | |||
| 82fd468519 | |||
|
|
579a936c5c | ||
| 3828832230 | |||
| 86029bc54f | |||
| 9ae2cd45ed | |||
|
|
3c5c0c62b5 | ||
| e8cae23ca9 | |||
|
|
ca537eda92 | ||
| 99a9153a60 | |||
| a0fbf00580 | |||
| 450b36aa96 | |||
| 31efb58ef3 | |||
|
|
6f8b771ab6 | ||
| 99df0e971e | |||
| 420c34aec2 | |||
| eac68f4185 | |||
|
|
269cd5cab8 | ||
| 76b84d714e | |||
| 522db1d462 | |||
|
|
8ced49783e | ||
| e2f6061fa7 | |||
| f44d1c7983 | |||
| 580460dc11 | |||
| 5ed284a1d1 | |||
| cd50ceb819 | |||
| db0908bad8 | |||
| d62222507a | |||
| 930f7c8e86 | |||
|
|
3226150ee5 | ||
| 888745d3e5 | |||
| 2e4af70139 | |||
| 5b9315bf71 | |||
|
|
9fd6827605 | ||
| 47767eea0d | |||
| 12308efbed | |||
| 2a07ba2f39 | |||
| 672ab973c8 | |||
| f25cbe3a7a | |||
| 471e1f0099 | |||
| d03848cc3d | |||
| 0c93056d66 | |||
| 457340668d | |||
|
|
c97b516378 | ||
| 67f969a6b8 | |||
| 02313d3a33 | |||
|
|
3c2cd68edf | ||
| 20a8078a2c | |||
| f3a4ce2ca8 | |||
| 0348b0c91e | |||
| 90eaa66e9c | |||
| 4b33cb660f | |||
| fe2c5676e1 | |||
| f3cbc2051a | |||
| 9272af316b | |||
| 333741d1ef | |||
| 122d48d493 | |||
| 8e8b40c70e | |||
| 4a1e128122 | |||
| c2a3797b33 | |||
|
|
85c9e50390 | ||
| 244672c64e | |||
| 22cd674189 | |||
|
|
b61ffa7235 | ||
| acedec0db1 | |||
| bc7fe194c7 | |||
| b6cba39abf | |||
| 69ff0e0035 | |||
| a5a9d20941 | |||
| 8fdf9f96e7 | |||
| 51c9d71e02 | |||
| 04c1927c96 | |||
| f619b2eee9 | |||
| 0646bec94f | |||
| 291620b1f0 | |||
| b7131f34e1 | |||
| 91fde08f52 | |||
| 7981adf359 | |||
| 7443963582 | |||
| a392d44799 | |||
| cadc00fd3a | |||
| 59417c16a9 | |||
| d2cb300072 | |||
| fe58dd74e8 | |||
| f5e12ba33d | |||
| 77b26ad788 | |||
| 0cfe3a45bd | |||
| 9a3d200253 | |||
| 65fd3c14bd | |||
| 4b1d77cc86 | |||
| f5ad4345bb | |||
| 3fc7c0c233 | |||
| 440d00ec6b | |||
| 4a9233c949 | |||
| 710ff5f7d3 | |||
| fb295292be | |||
| 863d69af78 | |||
| 4dff9b5357 | |||
| a13c48dd8f | |||
| 48d7bb8dfd | |||
| 1870113eb0 | |||
| 8f4e7432ff | |||
|
|
5ba685e12b | ||
| 8641d7610c | |||
| 400a2d50d4 | |||
| fdd8518474 | |||
| 0f1a799014 | |||
| 2bfb2c1329 | |||
| d35b600cd3 | |||
| e78da93a1a | |||
| d8ea389a70 | |||
| 6ac2d4d0e8 | |||
| 1e19f72fc4 | |||
| 6443ab6c14 | |||
| 1ed4f3f5e2 | |||
| aaf34cd373 | |||
| f9e71e5a9e | |||
| 843d97c632 | |||
| b57e6ca305 | |||
| ba7737d0ad | |||
| 86d62a1bb5 | |||
| 1d5b727465 | |||
| 01a2e984ad | |||
| 2589f91995 | |||
| 9aa9fa9abe | |||
| 5cc7030b85 | |||
| 2a3d273f99 | |||
| 6a6351a275 | |||
| 0bec263d78 | |||
| fc56af4ff1 | |||
| 5d5796b8fb | |||
| c0a46b778a | |||
|
|
33cfc8e4d8 | ||
| 8e225141ad | |||
| 553a17edaf | |||
| d7be58a4fe | |||
| 7fe9e1f418 | |||
| 2bcf7b78a4 | |||
| 5aa0276f11 | |||
| 988468bc82 | |||
| cdd21c7a23 | |||
| ce1678626d | |||
| d620bed337 | |||
| c49ce8a690 | |||
| 5fc7be78c1 | |||
| 98a7ccbae8 | |||
|
|
7968f39f10 | ||
| c2334057b3 | |||
| e0286b2392 | |||
| 3df0d1463a | |||
| 3e0d7ec68b | |||
| 5892819542 | |||
| c2f025e59d | |||
| b34595bfa5 | |||
| 306f57c4d0 | |||
| aaa8d9d533 | |||
| 8fc34ef870 | |||
| fd211cd949 | |||
| d1e8a0800f | |||
|
|
b2415446a1 | ||
| e09ac0686e | |||
| 27f7f9a1dd | |||
| 7587f496d3 | |||
| 55f79f7c01 | |||
| 9fe5c84b47 | |||
| 74ed9b9ef7 | |||
| 64f498588f | |||
| 00d3f3d947 | |||
| e9426bf18a | |||
| 06070dcac2 | |||
| 005f169e48 | |||
| abdfa6c6eb | |||
| 1cb7a1ed77 | |||
| 33db2fd2da | |||
| 4742d437dd | |||
| 7b06f33496 | |||
| ef2ad3d19f | |||
|
|
9406856e59 | ||
| 77bbb3f7a7 | |||
| daebfe1c57 | |||
| ef35a22f93 | |||
|
|
ce40682a3b | ||
| 67ca803026 | |||
| 58c49080c8 | |||
| f7c5233d28 | |||
| 37b0c85a7a | |||
| 3c69c2c209 | |||
|
|
d7deaf139a | ||
| 14fcafc9a5 | |||
| 2f37ad1200 | |||
| f77ab5e980 | |||
| be99c96ac6 | |||
| 6adf0fda8e | |||
| 4f463ed26e | |||
| 2c49d9aee9 | |||
|
|
670be4fcf0 | ||
| 7db416f718 | |||
| 96f8f25d6e | |||
| 290a997444 | |||
| 41e2e10540 | |||
| a5ba70d40e | |||
| 340fdc4943 | |||
| abf0c43582 | |||
| 781366694c | |||
| 49135aefab | |||
| ad28da979e | |||
| 7a3b8aab82 | |||
| 7b1e7ac1ae | |||
|
|
29080bd731 | ||
| b812866d87 | |||
|
|
793efdfb45 | ||
| e98d649876 | |||
| f467b598a3 | |||
| 016af92a05 | |||
| 74c3c456b9 | |||
| 793e7a5f15 | |||
|
|
b779e21375 | ||
| d2ccc2d15e | |||
| c15c9b8b09 | |||
| 0a11f2d36b | |||
| 80dee41e9e | |||
| 7275e7b475 | |||
| 4efa10c3e0 | |||
| bd39681b54 | |||
| 4c1384f64b | |||
| bea2d1ed0b | |||
| 6035870c1f | |||
| ace6e49380 | |||
| b084791809 | |||
|
|
bd97cee551 | ||
| 88541106b7 | |||
| b419139a45 | |||
|
|
c8f1b443dd | ||
| 9dd26d3bdf | |||
|
|
bf9ef7b47d | ||
| 28c9ec6a6c | |||
| 463b2a6558 | |||
| 593cc1c105 | |||
| a559cb4e2e | |||
| 9f3730c11c | |||
| 71bb333aa4 | |||
| 1826cc865b | |||
| f0a70805c6 | |||
| 018fbd3771 | |||
| 16961aaff6 | |||
| 69728b23de | |||
| 45507970b9 | |||
| 7c3811e624 | |||
| 479c9fff69 | |||
| 568f3fab4b | |||
| e736214417 | |||
| 2177024fdc | |||
| f554feae82 | |||
| 8477cbe90e | |||
| 36c8d6ac5d | |||
| 86d3c10b05 | |||
| fc39d6d452 | |||
| 013ce912f9 | |||
| a91ee19ff8 | |||
|
|
e697b0b1bd | ||
| 3e77b4505c | |||
| efd76ef4c2 | |||
| 29e448c3bd | |||
| 644af71b73 | |||
| be11b8359d | |||
| 53657299a7 | |||
| bb805a11fe | |||
| a476ead7dd | |||
| c59b82490c | |||
| c24d5b9e70 | |||
| 27ca410b86 | |||
| 44d663d176 | |||
| d556756283 | |||
| ad2634e32a | |||
| 9947c203fb | |||
| 452e541ecd | |||
| 1cb038fbc2 | |||
| 0cee08b087 | |||
| b7cb3c6a7c | |||
| e8b91fac09 | |||
| d2ab4feda0 | |||
| a2d92442b6 | |||
| 75df581156 | |||
| 010b63b438 | |||
| 7646f8a909 | |||
| ddb6099db2 | |||
| e5d32ec503 | |||
| 2ef8f17eb2 | |||
| 50743fecf0 | |||
| 3801c29cf6 | |||
| 360d0249b1 | |||
| 944c947e3b | |||
| 918659c3ea | |||
| 3db14eb5b6 | |||
| a0bb408b6a | |||
| 358d981c2b | |||
|
|
b193159441 | ||
| ebd709d4cb | |||
| 50ca46b5a9 | |||
| 9b3e8960bd | |||
| 42f2cbbe7e | |||
| eb73cc4027 | |||
| f23995005c | |||
| e2554a7257 | |||
| 5ad647c975 | |||
| a198d13a5f | |||
| 96bf2572a0 | |||
| 6bc337e23a | |||
| 0dd51a6f38 | |||
| f4b061ee8a | |||
| 61dada268a | |||
|
|
123bcc0559 | ||
| 8ae435998f | |||
| 580db7a9d6 | |||
| d613d747fc | |||
| aa95b145d1 | |||
| 566cfaa124 | |||
| 3019a5fb10 | |||
| 9b184d3df3 | |||
| b508eb11fd | |||
| b87930b309 | |||
| fb4c9deca7 | |||
| 957453f4c7 | |||
| b94f55cf25 | |||
| 0bc4abc285 | |||
|
|
981472382a | ||
| 2070985e32 | |||
| 9bed188175 | |||
| 2379a8a09f | |||
| 7834d078b9 | |||
| 7be395233c | |||
| 5e04905b8e | |||
| b9fd2d0760 | |||
| f66b5f1b9f | |||
| 89e645a0cd | |||
| 3026fec00c | |||
| 4ec4e95819 | |||
| 131701195f | |||
| 3550b6a75a | |||
| 0420690853 | |||
| b6ea99b75c | |||
| 86e8ae40de | |||
| 308261b2be | |||
| b1ded69873 | |||
|
|
583a7d3465 | ||
| bbdd04b36e | |||
| 20a45ef0c8 | |||
| c003aae57f | |||
| 2c9d8fe6aa | |||
| 739c92c87f | |||
| d40ed903a5 | |||
| d2e553643b | |||
| 71100a4634 | |||
| afe83cb82c | |||
| 22a63a2c32 | |||
| 12a4880f16 | |||
| 76f4831836 | |||
| 340ea049eb | |||
|
|
896d127a07 | ||
| 1ec7e62756 | |||
|
|
dc02763fd3 | ||
| 2b6cd1d21b | |||
| c35bccd8c0 | |||
| 96dee76466 | |||
| d5643868b6 | |||
| 9e96b89aa0 | |||
| 3997d9563e | |||
| ee06a96792 | |||
| a72e781e02 | |||
| e83574a452 | |||
| 7300b52d25 | |||
| 0d087399bc | |||
| c720655d3f | |||
| 3b13170a01 | |||
| 7412a2cc0a | |||
| f6436911f1 | |||
| 8fb8659062 | |||
| 862da62226 | |||
| 199b13ad75 | |||
|
|
4ebc825b16 | ||
| 5ba3ac9dd9 | |||
| b6c4f1e4bc | |||
| 0e5e967920 | |||
| dcfc61e7a9 | |||
| d81bf80ada | |||
| 7a5fad6659 | |||
| e7e298620d | |||
| 489752602f | |||
| 7163328a59 | |||
| 13326b6aea | |||
| bd927133d7 | |||
| b1e3eac74c | |||
|
|
61b816fcd7 | ||
| 250bb48c9c | |||
| 4937fdb9f7 | |||
| 3b740eae3c | |||
| 686d09a599 | |||
| 3b24e6c3d7 | |||
| 3439fd57ff | |||
| b2f17234fa | |||
| 1fdb93968a | |||
| e5baf46dce | |||
| 2a8ece6fcc | |||
| 55182c4138 | |||
| 3f68bbad63 | |||
| e16ad97838 | |||
| 5ad4c617c0 | |||
| c3f4ba80bb | |||
| 1594a88daf | |||
| 5048008be6 | |||
| 23e28feae2 | |||
| 4b1e89a7d8 | |||
| a11053db54 | |||
| 9dfc11c033 | |||
| 9fde93c0c4 | |||
| f65032e4c9 | |||
| ade50ca213 | |||
| e584c1119b | |||
| 57ec39bb51 | |||
| 04e979911c | |||
| 39db344ebd | |||
| 188a993d84 | |||
|
|
7404634af6 | ||
| b99ab9d138 | |||
| 08080d4b99 | |||
| a2a70e28eb | |||
| 25f7f534d5 | |||
|
|
34a0618d59 | ||
| fc68a96592 | |||
| d1d300eedd | |||
| 62cd92710b | |||
| 5ba452d12f | |||
| 808dd9dffc | |||
| e89881e589 | |||
| f24da643ec | |||
| c79a711fd3 | |||
|
|
b1d4588de1 | ||
| 354d4a321a | |||
| bcfd1657f8 | |||
| d9d8f80e72 | |||
| 660354d8f4 | |||
| d32c044b2e | |||
| 3345d63f20 | |||
| aa7bb25b36 | |||
| 6fc81eeb3f | |||
|
|
6d2b567ba5 | ||
| 37144b9a9f | |||
| ea7d504f95 | |||
| 9be83af3c9 | |||
| 9a0698ae07 | |||
| 6e4981a02e | |||
| 05a1437bca | |||
| 01837d3755 | |||
| 0ff97e74ff | |||
| 4b95814719 | |||
| 6d001caaab | |||
| 0d48b17866 | |||
| e71ef4789d | |||
| f19a282f63 | |||
| f4ae141981 | |||
| 135f3f08e4 | |||
|
|
c073ed3c27 | ||
| 7aa99fa987 | |||
| 7835797701 | |||
| 1eda7744f0 | |||
|
|
1f680d89b4 | ||
| d6d67940a2 | |||
| 652933867d | |||
| 602ce766bb | |||
| 204f3138c9 | |||
| ae55407219 | |||
| f28515fcf1 | |||
|
|
babbca7fcc | ||
| 86b0d62a54 | |||
| 0e0264ad35 | |||
| 8b2b509c5c | |||
| 8710adad62 | |||
| 3e9c16da28 | |||
| 56a980d2ea | |||
| f8f85b0092 | |||
| 426be81077 | |||
| 0566a34093 | |||
| 71e2d7fc67 | |||
| d0d77c9b9b | |||
| 3c8a8c31d5 | |||
| b9bc5bbe64 | |||
|
|
fb485595a6 | ||
| 166ad5bf4b | |||
| d20a6b9e8d | |||
| 4107551a09 | |||
| 3aed840c77 | |||
| fb968fbef7 | |||
| 6c917b5486 | |||
| 8d48e3b317 | |||
| 2b3a19bbcb | |||
| f7ac46d9ab | |||
| f86a0aa6ed | |||
| 98adcaad7c | |||
| f973d59d58 | |||
| ac89a7237c | |||
| 1339b43fb7 | |||
| 24dc0cefdd | |||
| 9c21bf4a06 | |||
| e0638cb60c | |||
| 2b3de00f4c | |||
| 0fbbc40237 | |||
| 067b1856d2 | |||
| 5e0a6a0fd6 | |||
| afb31253c8 | |||
| dc346a5654 | |||
| abdb638295 | |||
| dfba5f1021 | |||
| 47cd969f10 | |||
| 00d9b3eb47 | |||
| c8a5322e39 | |||
| 703c2eb296 | |||
| a74a7a9aa5 | |||
| c89e773c8e | |||
| d3e3e90ddd | |||
| a875509703 | |||
| 85b8bbec04 | |||
| 3713e3fc11 | |||
| 9a7c3ad0f4 | |||
| 1badf2354b | |||
| f1bbb2abe3 | |||
|
|
30a8fe202e | ||
| 4b1388bec3 | |||
| a1ec74e5d9 | |||
| 004381fdc6 | |||
| d41f46ad17 | |||
| 70bcf1a4c0 | |||
| 1b6d7ca160 | |||
| a60e22d419 | |||
| 2bf5a69f23 | |||
| 6ed9992895 | |||
| a18360b5ac | |||
| a49251c0bb | |||
| 65d76e11ea | |||
| 89f9d0c2f3 | |||
| 3f132fb60b | |||
| 11bea160b9 | |||
| 5708434b7e | |||
| 28c3d74c31 | |||
| 15936c3a1a | |||
| 59f43a6a87 | |||
| caa78ed9d5 | |||
| 8c0905c39b | |||
| 5d43137f3c | |||
| b58fdb1361 | |||
| 13fe7f1061 | |||
| d8bf78c7b4 | |||
| a75025c1a0 | |||
| 4414fad940 | |||
| 41bca90452 | |||
| 777f16f8e5 | |||
| aaa05e9b1e | |||
| d442d4f23a | |||
| ed98f5434d | |||
| 269b33fa7f | |||
| 0c04b6ebcd | |||
| 113b00b8e6 | |||
| fec9d3750b | |||
| b0727ff8a1 | |||
| e2da707fdf | |||
| 8cececccc1 | |||
| ae1cdea043 | |||
| f63c69d904 | |||
| 5b67057ebe | |||
| 85b331f51b | |||
| 2346b0606a | |||
| 6bff30df32 | |||
| e3b25ce1f4 | |||
| 746db59438 | |||
| 9ed0fed596 | |||
| b58e4d86cf | |||
| 89935a9fdc | |||
| 4fe59969a9 | |||
| 52a80f082a | |||
| 71b4881885 | |||
| c8830380e5 | |||
| 39a3a129d6 | |||
| cbe1f208c3 | |||
| c15836a15d | |||
| 3063a6b5a2 | |||
| 2a2a655002 | |||
| 5e7d806b7f | |||
| ca064b75d2 | |||
| aefd1e8cc5 | |||
| 6e5f5bb4d3 | |||
| bdeca100d5 | |||
| 792fcf900a | |||
| 266ae61e53 | |||
| db48bcdb8d | |||
| 34b9ba3097 | |||
| f704dfdeff | |||
| 1262310af9 | |||
| 3dd0c0516d | |||
| adc31a21b2 | |||
| 0f09da42e9 | |||
| e1f10bb82f | |||
| 3a8a783804 | |||
| 6bb827e97a | |||
| e008c094e5 | |||
| ab385bfbb5 | |||
| 69673a0d36 | |||
| acd148b70b | |||
| 7e831763e5 | |||
| 150f86df4a | |||
| f29a833016 | |||
| f5e050c0b3 | |||
| 4be0788af8 | |||
| 1abce14674 | |||
| da10692f57 | |||
| 5d220b1430 | |||
| 89dd33bd99 | |||
| 25a24c5916 | |||
| a82f92ea6c | |||
| c9ae933045 | |||
| b1c23eb5e8 | |||
| 866c78a6b2 | |||
| b3186cce3a | |||
| d96798b6b3 | |||
| 221bc5b2cb | |||
| 0fd5a91a2e | |||
| b0b785bcb0 | |||
| 14af04f027 | |||
| 0eb659ce54 | |||
| be1283d6dc | |||
| c952489736 | |||
| 1a54aefaf7 | |||
| 7c72a40bde | |||
| 5b61c9718d | |||
| 1378cf4b57 | |||
| dbfadd4cc3 | |||
| 71cc73397d | |||
| 886a185c5d | |||
| a02d251c71 | |||
| 3a393e9bda | |||
| eaf9207610 | |||
| 8ddf17e42a | |||
| a5b67c20e6 | |||
| c6345da817 | |||
| 4b2d8bc2ed | |||
| 6adebce0f8 | |||
| 4e176d7b9e | |||
| 4d097b4589 | |||
| 18d457efa6 | |||
| 70734dfd83 | |||
| d7ce34a6cc | |||
| 2350b6cf53 | |||
| 7eaf29e912 | |||
| 5f632dfca0 | |||
| 48f21805cb | |||
| 9787e5d2c8 | |||
| 5e50b896fe | |||
| c2d7d841de | |||
| 1a74834831 | |||
| 86f77e0a5e | |||
| 0f4739b7b1 | |||
| 76d5d8348b | |||
| a7c7025667 | |||
| 709e9d2d2f | |||
| 181467beaf | |||
| d706b8b320 | |||
| 7fece66ef6 | |||
| 3e0a4f113f | |||
| f890c78e83 | |||
| 8a2437051b | |||
| e26963c3a8 | |||
| 8d4bdc992e | |||
| 165240076a | |||
| 8b3173ed95 | |||
| 090b30bb58 | |||
| e952b515d8 | |||
| 8389360eba | |||
| fd768a208c | |||
| 5256e40989 | |||
| e8e31758c1 | |||
| 16f8566ac3 | |||
| c65e73d2ac | |||
| bad1b05de0 | |||
| b50b488fc2 | |||
| 8995020117 | |||
| 159decf760 | |||
| eb8a0061ce | |||
| f5494f4b31 | |||
| 18c11fb229 | |||
| 6c5a7dbdcb | |||
| c92f784967 | |||
| 85b828a2bc | |||
| cfe14cb55b | |||
| 1e0a49a1b5 | |||
| a6e5b87200 | |||
| 3ee20e9fc8 | |||
| 6696066f91 | |||
| f0cc8e191e | |||
| 2f9834acd5 | |||
| baf2218980 | |||
| f6fb7f4747 | |||
| a714359ea9 | |||
| f24e6f4681 | |||
| 70f1662929 | |||
| 2e67a95b89 | |||
| 23fe8d2124 | |||
| e8ecfce4fd | |||
| b8ca685473 | |||
| 0d1b8f5e65 | |||
| a91463d9bd | |||
| 549bbfbccf | |||
| f5208e95fe | |||
| 7cb47ecea7 | |||
| cd352b81a8 | |||
| 87975e16f1 | |||
| df838d1dee | |||
| 002b5a3de5 | |||
| 1700f096ae | |||
| 68f3ecc6f9 | |||
| a504f6bd0e | |||
| 4e3678d498 | |||
| 5ee0e67512 | |||
| 7c71b023c7 | |||
| 479facee52 | |||
| b0f452952f | |||
| 577adab857 | |||
| 85abb6c520 | |||
| 35e410f832 | |||
| 33ecc44e1f | |||
| 89c6ba3ac0 | |||
| 1ecf3e8d8c | |||
| b9d0e63beb | |||
| 1145144a98 | |||
| 0422411c89 | |||
| 921703a5b5 | |||
| e300558db8 | |||
| b35b7f1d7a | |||
| 1746444975 | |||
| 8e292966c9 | |||
| 1e0a841447 | |||
| 85746809af | |||
| e34e30f776 | |||
| 0f11df6f95 | |||
| ac0503c6e9 | |||
| 8221d4b723 | |||
| cd58375f02 | |||
| 9511d3209c | |||
| 59124f1d1f | |||
| a51be51c03 | |||
| 559ebf200d | |||
| 6336898978 | |||
| af294ff9ac | |||
| 26383f1cde | |||
| c41a59ad1d | |||
| 27035d8be7 | |||
| 2c4155b674 | |||
| c9e439c0f4 | |||
| bebfbafae8 | |||
| 96b9d4d1a5 | |||
| e12ad5212d | |||
| 20791b6efe | |||
| 675c4bcd3c | |||
| e0aa1406e2 | |||
| 743ba4a937 | |||
| bb1aa4044a | |||
| 34cc6325da | |||
| 0acef08f09 | |||
| 1b2705291d | |||
| a85d205cf3 | |||
| b38ebb92c0 | |||
| c8ec18f912 | |||
| ce0a98c7a8 | |||
| 309b661fab | |||
| 6efc4e157b | |||
| 095fd15607 | |||
| aec62e3eb2 | |||
| ea85dd5f25 | |||
| 8f3eeac987 | |||
| cdbfa4e1b0 | |||
| 5aad7af88f | |||
| 2fdb32d1d6 | |||
| 463ff29c19 | |||
| abfc337978 | |||
| b604abfdae | |||
| a3abbbb9e1 | |||
| f12d1834aa | |||
| 3e2805b140 | |||
| 5c2ca9cc83 | |||
| 324ab2d21f | |||
| 5eb3881b28 | |||
| 87a5577721 | |||
| 8c9c5c192e | |||
| 071c6a010e | |||
| 0878f9498d | |||
| 2059f85800 | |||
| 3bb09081d4 | |||
| 047d8c3028 | |||
| 7b1cf0e660 | |||
| dd68825329 | |||
| 5341d2a6b8 | |||
| b4084913de | |||
| eb7dbe4663 | |||
| 9d6ecedba2 | |||
| 64198f0562 | |||
| 2d64308b78 | |||
| 0ca13206fe | |||
| 5d8577725a | |||
| c10708ee23 | |||
| 5b56b7367d | |||
| 08bf6984cd | |||
| a9918edccf | |||
| 7a88ce1576 | |||
| 8a21347547 | |||
| 4da69b5c09 | |||
| 2be8f427c9 | |||
| 719996dc07 | |||
| cb4c6a4a2e | |||
| ab58467fc0 | |||
| 7572fc2d90 | |||
| da72383619 | |||
| 93a972eb55 | |||
| 44a003b787 | |||
| 7ad7e9be41 | |||
| 47ebeba041 | |||
| 3d39929180 | |||
| 31812f701c | |||
| 8f0688f46d | |||
| a62304baaa | |||
| 1f840c7c04 | |||
| 4633c209a6 | |||
| 5965f2a4ca | |||
| 7eb8322d71 | |||
| b6f5e801a8 | |||
| 45b22c780a | |||
| 7cce8ca1a6 | |||
| b250f24880 | |||
| 6f2dd4d30a | |||
| ff91b98a44 | |||
| bc77b62685 | |||
| 872b613d87 | |||
| c799124d12 | |||
| 4cdfab275e | |||
| 665a9d4655 | |||
| 35d47c7a53 | |||
| 39103b5ec6 | |||
| 1a2e49c605 | |||
| 135b7bf30d | |||
| 99f429f7ed | |||
| 492430ea8a | |||
| 65b9c41dbf | |||
| 2d45eb8fa1 | |||
| f73d7f0d39 | |||
| 0245fcf317 | |||
| af77f42eeb | |||
| 0267086a32 | |||
| b86c63460b | |||
| 31e41cd949 | |||
| 49cb38ff4f | |||
| be9636a846 | |||
| 26a13b1276 | |||
| 34c2673504 | |||
| 743e182949 | |||
| eac63202f7 | |||
| 95869f70c4 | |||
| 35360ef7a8 | |||
| c01846ca1f | |||
| 2596659125 | |||
| 1c4dd55396 | |||
| 905d04957d | |||
| b286fa4de1 | |||
| b62d213000 | |||
| e8bed84e6e | |||
| c59c2d9f2a | |||
| 38ed968cc5 | |||
| 8203389ec0 | |||
| 20d18c8ba8 | |||
| 4bd64e4b36 | |||
| ad4a2c42a5 | |||
| a61a62adcc | |||
| c316c1715a | |||
| 69ee452af7 | |||
| a52d01da80 | |||
| 87ed2174d8 | |||
| 632322fa88 | |||
| b9a82ea08d | |||
| 89c14a69b8 | |||
| 632ce1c4dc | |||
| bfa360ce24 | |||
| bddeff5a0c | |||
| 80f58b9d24 | |||
| 156c5849c7 | |||
| 1e79d9616a | |||
| 97cf8dd5a8 | |||
| d33f24aa6b | |||
| 169d361522 | |||
| 0bd6cb95f5 | |||
| 6e2bfebc60 | |||
| e60952e75b | |||
| ed34d8d454 | |||
| fd3778cde1 | |||
| 187999ccfc | |||
| ea8a63b952 | |||
| 1f5e18985f | |||
| 10fd3ee7da | |||
| 5db3d81d25 | |||
| ebad6f044f | |||
| 9661171851 | |||
| 8d15c645a9 | |||
| 98d969fece | |||
| 48f2552311 | |||
| 0d55e17842 | |||
| 60685547b6 | |||
| c111b2d432 | |||
| c959e5e5de | |||
| d3cfe29e14 | |||
| e0003be06b | |||
| 473f762bd9 | |||
| 54409c9f12 | |||
| b04628f890 | |||
| 29bee2e1f5 | |||
| 7af3cb042f | |||
| a80224b07d | |||
| 9a09993336 | |||
| 8815028589 | |||
| 15d2b5f7dd | |||
| 336495db02 | |||
| c0ded5607c | |||
| 42d5d274b2 | |||
| 10f0d1f692 | |||
| 2f38afaef7 | |||
| 835f9a5f64 | |||
| 4fc518dd4b | |||
| d4ec08179e | |||
| 6cc59da699 | |||
| 4ed4caf55b | |||
| 857970c230 | |||
| e616f844ab | |||
| 4b955eeb63 | |||
| 2f32c9e22b | |||
| 13c5562ffe | |||
| e01d3e1e59 | |||
| f9bf85e702 | |||
| 015fd41ba5 | |||
| 07776f489c | |||
| 296eb7c20b | |||
| c125937f05 | |||
| 79ddd42321 | |||
| 9aaf6e0125 | |||
| b8f375480d | |||
| 92374d311f | |||
| 4bd0a0f1b3 | |||
| c126670789 | |||
| d4bc7a66bf | |||
| 7a7505193d | |||
| 51894cbbb5 | |||
| 7f603656f3 | |||
| d47f3aa19d | |||
| ce939eb7de | |||
| 606069056e | |||
| 9945dbc373 | |||
| 8c3392253f | |||
| 19019936bc | |||
| 65e35ec38e | |||
| 6e6ce55bb8 | |||
| d708546c27 | |||
| 823f161451 | |||
| dc53adec3c | |||
| 04704e67cf | |||
| e7b72e519a | |||
| d21533a446 | |||
| 4f081aaa71 | |||
| 0be02d40c2 | |||
| d7a86d3a49 | |||
| 499cf41db2 | |||
| de7ef32dbb | |||
| e8e036173d | |||
| 2023b9ab75 | |||
| 64c9cda54e | |||
| b979761e8c | |||
| 156879f161 | |||
| 4facb82bb3 | |||
| b0c58616ff | |||
| cd875f52cd | |||
| c491d2a4ff | |||
| dc65cfdb02 | |||
| 795e0f29d6 | |||
| 246001a1f2 | |||
| c46a2725c2 | |||
| bb356cd7b0 | |||
| 4c038c3f26 | |||
| 5c85b21b1d | |||
| 084a5a4b19 | |||
| 7c50ec748f | |||
| 5c1b449593 | |||
| c1083a1a33 | |||
| ab842f5703 | |||
| 0a6e6beaef | |||
| 9eecd70088 | |||
| 527d6521ac | |||
| 52d8b17914 | |||
| 427c97bfb3 | |||
| a8f7b0c375 | |||
| 847c8d2a42 | |||
| 6766ed562a | |||
| e9f1adc699 | |||
| dad9a42ece | |||
| a348bda2da | |||
| 69d94e8181 | |||
| 4e68ca4bae | |||
| 10f392408c | |||
| dad943059f | |||
| 3fbb441d13 | |||
| 8a91224f66 | |||
| 536d480a36 | |||
| bfc457115c | |||
| 430be41982 | |||
| f8bad0558b | |||
| 3e2c676530 | |||
| 2a01540348 | |||
| 362631a6ec | |||
| a4f0d7c13b | |||
| aaf7c71628 | |||
| 378a569316 | |||
| 23f31d359a | |||
| 4e66128ea4 | |||
| 84d64ed53f | |||
| a95b0ea026 | |||
| 5bfba4b202 | |||
| efb6421289 | |||
| 65e0a5d024 | |||
| 1bf1e6da3b | |||
| 4b791bf12d | |||
| 105d4f355b | |||
| 5b3178e049 | |||
| 5dd679698b | |||
| dbe837a52e | |||
| 39635c1115 | |||
| ac1749a9bb | |||
| 180bb9c6b9 | |||
| 7a5f75eedc | |||
| 1ae4bba960 | |||
| 0e8876ce4d | |||
| 766b79cfd3 | |||
| acb32ba190 | |||
| f1326f4a75 | |||
| a748d50b0a | |||
| a260c776cc | |||
| 10494cc160 | |||
| c7ee1fadab | |||
| 39b6f656f6 | |||
| a37dd5e792 | |||
| a022348e47 | |||
| 70610fbd70 | |||
| 8716d80efb | |||
| cf161359c2 | |||
| fa5bf4fe24 | |||
| 17740d864f | |||
| 11c78afbbe | |||
| e28353a86b | |||
| 138b631a65 | |||
| d5e2d35672 | |||
| 9ad6be02bf | |||
| 8a2bed5877 | |||
| feb652a026 | |||
| 7cb13c09e7 | |||
| de2fdb61bc | |||
| 1caf381603 | |||
| df2bd5b624 | |||
| cd8f4945e6 | |||
| 8a3893b36b | |||
| 4994050839 | |||
| df8d6c42c0 | |||
| 1017a3a003 | |||
| bd935810bf | |||
| d89ba9832a | |||
| 2b49245901 | |||
| f5ec5b320d | |||
| b2037ddf92 | |||
| ce469cbae3 | |||
| ac3711768c | |||
| 94eb099fb9 | |||
| 9085fa7a7e | |||
| 2b29a3c012 | |||
| 356027981f | |||
| bd87ffadff | |||
| 71022d13b1 | |||
| 108ad59bd9 | |||
| 828ee4f2ed | |||
| d8494e439c | |||
| 891b94a889 | |||
| 0b6adc9244 | |||
| 08d5987553 | |||
| e5c70b1b43 | |||
| 24659e324f | |||
| 175be313d2 | |||
| a4f8b114bb | |||
| 165f748850 | |||
| 55b2d30837 | |||
| e39ad5a2f9 | |||
| e6be0fe09f | |||
| aed9ec74a5 | |||
| ca7d6f8b54 | |||
| f03399369d | |||
| fdb5c9818d | |||
| 2ea3b16828 | |||
| eb222ae98a | |||
| 2f01932b43 | |||
| 7b799ee7d8 | |||
| 75c3ca9ffc | |||
| 5a71144588 | |||
| 5001003414 | |||
| 0407659445 | |||
| 886cc8b6bf | |||
| 9fa2d7067a | |||
| 06757d9a83 | |||
| 1783ef3804 | |||
| 4f968d4420 | |||
| 73192e179a | |||
| 26b6a605ad | |||
| c44b9f0949 | |||
| 3dbd887702 | |||
| cbac440141 | |||
| 16bddd7866 | |||
| a8e32cadff | |||
| e45895b850 | |||
| 3e9d5f4108 | |||
| 121fb83695 | |||
| d4ed7b0e19 | |||
| d7337e9e1b | |||
| a33a7be954 | |||
| ed3fc48e34 | |||
| e61c1fb5dd | |||
| 16e0462a89 | |||
| 6cc520a579 | |||
| cdfb8894b9 | |||
| 1e023a1247 | |||
| 57da54ad44 | |||
| 6a14f221ee | |||
| 47dfb029f7 | |||
| 8f3c90718f | |||
| 2b81ab274b | |||
| dbdce97904 | |||
| 8a3b9ee2f8 | |||
| 9a220a85a7 | |||
| ee018f124f | |||
| 244e2e8d86 | |||
| 999b60999f | |||
| 416f350fdb | |||
| d81ff1dc7a | |||
| 0877aeb7d5 | |||
| b7d3427e62 | |||
| d75e12b67b | |||
| eebace5030 | |||
| b82213d97f | |||
| 46465207a7 | |||
| 72d2e3ad77 | |||
| a0b51cef62 | |||
| d633d96462 | |||
| c688f12c06 | |||
| dbd216aa3b | |||
| b6782198c0 | |||
| a3fd37fd84 | |||
| 1200350c62 | |||
| 2d20d913e3 | |||
| 6843ac5548 | |||
| da9b2d3406 | |||
| 23eb9ab80a | |||
| adf83f0867 | |||
| 67490f42e6 | |||
| 2fdc68e7e6 | |||
| 0069bdac9a | |||
| d1fafd760b | |||
| 42877f570d | |||
| c8d938591a | |||
| e76606d0a3 | |||
| 0614c12eef | |||
| 39f299e211 | |||
| 7d8a68b589 | |||
| cb76bea3e7 | |||
| d9ecb8400f | |||
| 94afffec8e | |||
| 83e3c199cd | |||
| 20b3303623 | |||
| 20456981d6 | |||
| fce9ad82ba | |||
| d494da3d3b | |||
| 5639b7936d | |||
| 91a4732c1b | |||
| 46c5647463 | |||
| f6dd0571a0 | |||
| c681d9697f | |||
| 4c178b17a5 | |||
| 889d6e4469 | |||
| 85dd4a6d97 | |||
| 2b8c38f474 | |||
| 712c5a4a40 | |||
| 9ad5fda8c4 | |||
| acfcba9d67 | |||
| b8da0f8c19 | |||
| 128203bb17 | |||
| 563e6c8631 | |||
| 3ab5c4ce95 | |||
| 7900333d41 | |||
| f0f505301e | |||
| 36f63aa97f | |||
| 9bf5ed087d | |||
| c2df3a1ffd | |||
| 6f452f25e8 | |||
| ffda1a2724 | |||
| 3aa3c6b0dd | |||
| 7db81b9bdd | |||
| 381ce558b9 | |||
| 35ce2562c7 | |||
| 55013d86cd | |||
| a4b6411fcd | |||
| 905dbe3b48 | |||
| e6e605f43a | |||
| 9aa8a9318c | |||
| ba957b4dcf | |||
| e611105b60 | |||
| 31209d2b30 | |||
| a9bb4b9224 | |||
| 1b0d170052 | |||
| 37add470eb | |||
| e36f56da30 | |||
| 71a13da619 | |||
| aa89b83746 | |||
| bc3d5ed4c7 | |||
| b1b51b0281 | |||
| f2591bff06 | |||
| 59d3f98110 | |||
| aef7699ba2 | |||
| bb30edde28 | |||
| b78c86a0ac | |||
| bcf3b761cd | |||
| 9801d86b61 | |||
| 23f24cf467 | |||
| 916497f329 | |||
| 184e407201 | |||
| 5ab51d66b7 | |||
| 3ca7538977 | |||
| 592bd52667 | |||
| 566d8e8c27 | |||
| a47d784f66 | |||
| 051c123b77 | |||
| e4531d3726 | |||
| e7385fdf9d | |||
| d9c7bd0b50 | |||
| f23734464d | |||
| 44bb3480ac | |||
| 24f28dc82c | |||
| 8db0fca114 | |||
| 85e79113fd | |||
| cc22beab3e | |||
| 3c466931c7 | |||
| 067af207df | |||
| d37433c026 | |||
| 2ec800fe62 | |||
| d5fd2c4827 | |||
| 92271d2981 | |||
| 8067e42f78 | |||
| af0a980d1b | |||
| 5f385e44c0 | |||
| 6f6a0e1bd1 | |||
| 7b8b0cc227 | |||
| d3258357e0 | |||
| 1bc4593654 | |||
| 35b45cbb5b | |||
| 1cd1ee0e10 | |||
| 3b45692ff1 | |||
| 301aa59d61 | |||
| 7064cd6e7e | |||
| aae0de25f1 | |||
| 1022824e47 | |||
| de6120b267 | |||
| 24252655eb | |||
| b810e72588 | |||
| 797b1c1f00 | |||
| 1f4df655ad | |||
| ab96277fb1 | |||
| 3c3c7fe251 | |||
| 5b243ab2da | |||
| 2bdc9410b7 | |||
| 6b68c42c33 | |||
| dd99496d75 | |||
| 49ecbc3259 | |||
| 2305380945 | |||
| 3aedae39e4 | |||
| 9382c30d35 | |||
| 03208332ae | |||
| e97bd27eb0 | |||
| 5185b52c91 | |||
| a08c63a021 | |||
| 66a5510873 | |||
| 59b701a6be | |||
| cc682b1945 | |||
| b3b7391712 | |||
| bf9625fc0b | |||
| a1b78de312 | |||
| 3f438238e7 | |||
| a982bb432a | |||
| cb374f663a | |||
| 510842727c | |||
| cc904e772a | |||
| c43d83d8c0 | |||
| ae87bc9ab5 | |||
| fdff9da488 | |||
| a15a1b229e | |||
| 675d3fa808 | |||
| a8afbc5cc8 | |||
| 5a7c138be0 | |||
| 4c04ce01c2 | |||
| f7ebd61f40 | |||
| e90ad9477a | |||
| 2fbf79bce0 | |||
| 3059003191 | |||
| 11c7b0e626 | |||
| e1ef45da2a | |||
| aae3cc3a06 | |||
| f1121dcc9b | |||
| b77ad7e5c5 | |||
| e3b14015fc | |||
| 1fe4b18266 | |||
| 5233b395d0 | |||
| 579f8d9565 |
37
.gitattributes
vendored
Executable file
37
.gitattributes
vendored
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
* text=false
|
||||||
|
*.php ident
|
||||||
|
|
||||||
|
# Force the following filetypes to have unix eols, so Windows does not break them
|
||||||
|
*.php text eol=lf
|
||||||
|
*.js text eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.css text eol=lf
|
||||||
|
*.md text eol=lf
|
||||||
|
*.txt text eol=lf
|
||||||
|
*.htaccess text eol=lf
|
||||||
|
*.json text eol=lf
|
||||||
|
*.c text eol=lf
|
||||||
|
*.cpp text eol=lf
|
||||||
|
*.h text eol=lf
|
||||||
|
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.jpeg binary
|
||||||
|
*.gif binary
|
||||||
|
*.ico binary
|
||||||
|
*.mov binary
|
||||||
|
*.mp4 binary
|
||||||
|
*.mp3 binary
|
||||||
|
*.flv binary
|
||||||
|
*.fla binary
|
||||||
|
*.swf binary
|
||||||
|
*.gz binary
|
||||||
|
*.zip binary
|
||||||
|
*.7z binary
|
||||||
|
*.ttf binary
|
||||||
|
*.eot binary
|
||||||
|
*.woff binary
|
||||||
|
*.pyc binary
|
||||||
|
*.pdf binary
|
||||||
|
*.dat binary
|
||||||
|
*.z binary
|
||||||
1
.github/contributing.md
vendored
1
.github/contributing.md
vendored
|
|
@ -1 +0,0 @@
|
||||||
A developer and contribution documentation can be found at https://orange-management.gitbooks.io/developer-guide/content/index.html.
|
|
||||||
11
.github/dependabot.yml
vendored
Executable file
11
.github/dependabot.yml
vendored
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "composer" # See documentation for possible values
|
||||||
|
directory: "/" # Location of package manifests
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
13
.github/workflows/greetings.yml
vendored
Executable file
13
.github/workflows/greetings.yml
vendored
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
name: Greetings
|
||||||
|
|
||||||
|
on: [pull_request, issues]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
greeting:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/first-interaction@v1
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
issue-message: 'Thank you for createing this issue. We will check it as soon as possible.'
|
||||||
|
pr-message: 'Thank you for your pull request. We will check it as soon as possible.'
|
||||||
24
.github/workflows/image.yml
vendored
Executable file
24
.github/workflows/image.yml
vendored
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
name: Compress images
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '**.jpg'
|
||||||
|
- '**.png'
|
||||||
|
- '**.webp'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '**.jpg'
|
||||||
|
- '**.png'
|
||||||
|
- '**.webp'
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: calibreapp/image-actions
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repo
|
||||||
|
uses: actions/checkout@main
|
||||||
|
|
||||||
|
- name: Compress Images
|
||||||
|
uses: calibreapp/image-actions@main
|
||||||
|
with:
|
||||||
|
githubToken: ${{ secrets.GH_TOKEN }}
|
||||||
11
.github/workflows/main.yml
vendored
Executable file
11
.github/workflows/main.yml
vendored
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
general_module_workflow_php:
|
||||||
|
uses: Karaka-Management/Karaka/.github/workflows/php_template.yml@develop
|
||||||
|
secrets:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
GH_PAT: ${{ secrets.GH_PAT }}
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
18
.gitignore
vendored
Normal file → Executable file
18
.gitignore
vendored
Normal file → Executable file
|
|
@ -1 +1,17 @@
|
||||||
*.log
|
*.log
|
||||||
|
.directory
|
||||||
|
Build
|
||||||
|
*.cache
|
||||||
|
.directory
|
||||||
|
Vagrantfile
|
||||||
|
vendor
|
||||||
|
bower_components
|
||||||
|
node_modules
|
||||||
|
*.log
|
||||||
|
.vagrant
|
||||||
|
.vscode
|
||||||
|
.sass-cache
|
||||||
|
cache
|
||||||
|
Cache
|
||||||
|
Libraries
|
||||||
|
.idea*.cache
|
||||||
540
Account/Account.php
Normal file → Executable file
540
Account/Account.php
Normal file → Executable file
|
|
@ -1,22 +1,20 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
use phpOMS\Contract\ArrayableInterface;
|
|
||||||
use phpOMS\Localization\Localization;
|
use phpOMS\Localization\Localization;
|
||||||
use phpOMS\Localization\NullLocalization;
|
|
||||||
use phpOMS\Validation\Network\Email;
|
use phpOMS\Validation\Network\Email;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,21 +23,20 @@ use phpOMS\Validation\Network\Email;
|
||||||
* The account class is the base model for accounts. This model contains the most common account
|
* The account class is the base model for accounts. This model contains the most common account
|
||||||
* information. This model is not comparable to a profile which contains much more information.
|
* information. This model is not comparable to a profile which contains much more information.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
class Account implements ArrayableInterface, \JsonSerializable
|
class Account implements \JsonSerializable
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Id.
|
* Id.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $id = 0;
|
public int $id = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Names.
|
* Names.
|
||||||
|
|
@ -47,7 +44,7 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var string
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $name1 = '';
|
public string $name1 = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Names.
|
* Names.
|
||||||
|
|
@ -55,7 +52,7 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var string
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $name2 = '';
|
public string $name2 = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Names.
|
* Names.
|
||||||
|
|
@ -63,7 +60,7 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var string
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $name3 = '';
|
public string $name3 = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Email.
|
* Email.
|
||||||
|
|
@ -71,7 +68,7 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var string
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $email = '';
|
public string $email = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ip.
|
* Ip.
|
||||||
|
|
@ -81,15 +78,15 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var string
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $origin = '';
|
public string $origin = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login.
|
* Login.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var null|string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $login = '';
|
public ?string $login = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last activity.
|
* Last activity.
|
||||||
|
|
@ -97,31 +94,23 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var \DateTime
|
* @var \DateTime
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $lastActive = null;
|
public \DateTime $lastActive;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last activity.
|
* Last activity.
|
||||||
*
|
*
|
||||||
* @var \DateTime
|
* @var \DateTimeImmutable
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $createdAt = null;
|
public \DateTimeImmutable $createdAt;
|
||||||
|
|
||||||
/**
|
|
||||||
* Permissions.
|
|
||||||
*
|
|
||||||
* @var PermissionAbstract[]
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
protected $permissions = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Groups.
|
* Groups.
|
||||||
*
|
*
|
||||||
* @var int[]
|
* @var Group[]
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $groups = [];
|
public array $groups = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Password.
|
* Password.
|
||||||
|
|
@ -129,7 +118,7 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var string
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $password = '';
|
public string $password = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account type.
|
* Account type.
|
||||||
|
|
@ -137,7 +126,7 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var int
|
* @var int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $type = AccountType::USER;
|
public int $type = AccountType::USER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account status.
|
* Account status.
|
||||||
|
|
@ -145,7 +134,7 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var int
|
* @var int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $status = AccountStatus::INACTIVE;
|
public int $status = AccountStatus::INACTIVE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Localization.
|
* Localization.
|
||||||
|
|
@ -153,184 +142,43 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
* @var Localization
|
* @var Localization
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $l11n = null;
|
public Localization $l11n;
|
||||||
|
|
||||||
|
use PermissionHandlingTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Has permission.
|
||||||
*
|
*
|
||||||
* The constructor automatically sets the created date as well as the last activity to now.
|
* @param int $permission Permission
|
||||||
|
* @param int|null $unit Unit
|
||||||
|
* @param int|null $app App
|
||||||
|
* @param string|null $module Module
|
||||||
|
* @param int|null $category Category
|
||||||
|
* @param int|null $element Element
|
||||||
|
* @param int|null $component Component
|
||||||
*
|
*
|
||||||
* @param int $id Account id
|
* @return bool
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function __construct(int $id = 0)
|
public function hasPermission(
|
||||||
|
int $permission,
|
||||||
|
?int $unit = null,
|
||||||
|
?int $app = null,
|
||||||
|
?string $module = null,
|
||||||
|
?int $category = null,
|
||||||
|
?int $element = null,
|
||||||
|
?int $component = null
|
||||||
|
) : bool
|
||||||
{
|
{
|
||||||
$this->createdAt = new \DateTime('now');
|
foreach ($this->groups as $group) {
|
||||||
$this->lastActive = new \DateTime('now');
|
if ($group->hasPermission($permission, $unit, $app, $module, $category, $element, $component)) {
|
||||||
$this->id = $id;
|
return true;
|
||||||
$this->l11n = new NullLocalization();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get account id.
|
|
||||||
*
|
|
||||||
* @return int Account id
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getId() : int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get localization.
|
|
||||||
*
|
|
||||||
* Every account can have a different localization which can be accessed here.
|
|
||||||
*
|
|
||||||
* @return Localization
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getL11n() : Localization
|
|
||||||
{
|
|
||||||
return $this->l11n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get groups.
|
|
||||||
*
|
|
||||||
* Every account can belong to multiple groups.
|
|
||||||
* These groups usually are used for permissions and categorize accounts.
|
|
||||||
*
|
|
||||||
* @return array Returns array of all groups
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getGroups() : array
|
|
||||||
{
|
|
||||||
return $this->groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add group.
|
|
||||||
*
|
|
||||||
* @param mixed $group Group to add
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function addGroup($group) /* : void */
|
|
||||||
{
|
|
||||||
$this->groups[] = $group;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set localization.
|
|
||||||
*
|
|
||||||
* @param Localization $l11n Localization
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setL11n(Localization $l11n) /* : void */
|
|
||||||
{
|
|
||||||
$this->l11n = $l11n;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set permissions.
|
|
||||||
*
|
|
||||||
* The method accepts an array of permissions. All existing permissions are replaced.
|
|
||||||
*
|
|
||||||
* @param PermissionAbstract[] $permissions
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setPermissions(array $permissions) /* : void */
|
|
||||||
{
|
|
||||||
$this->permissions = $permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add permissions.
|
|
||||||
*
|
|
||||||
* Adds permissions to the account
|
|
||||||
*
|
|
||||||
* @param PermissionAbstract[] $permissions Array of permissions to add to the account
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function addPermissions(array $permissions) /* : void */
|
|
||||||
{
|
|
||||||
$this->permissions = array_merge($this->permissions, $permissions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add permission.
|
|
||||||
*
|
|
||||||
* Adds a single permission to the account
|
|
||||||
*
|
|
||||||
* @param PermissionAbstract $permission Permission to add to the account
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function addPermission(PermissionAbstract $permission) /* : void */
|
|
||||||
{
|
|
||||||
$this->permissions[] = $permission;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get permissions.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getPermissions() : array
|
|
||||||
{
|
|
||||||
return $this->permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Has permissions.
|
|
||||||
*
|
|
||||||
* Checks if the account has a permission defined
|
|
||||||
*
|
|
||||||
* @param int $permission Permission to check
|
|
||||||
* @param int $unit Unit Unit to check (null if all are acceptable)
|
|
||||||
* @param string $app App App to check (null if all are acceptable)
|
|
||||||
* @param int $module Module Module to check (null if all are acceptable)
|
|
||||||
* @param int $type Type (e.g. customer) (null if all are acceptable)
|
|
||||||
* @param int $element (e.g. customer id) (null if all are acceptable)
|
|
||||||
* @param int $component (e.g. address) (null if all are acceptable)
|
|
||||||
*
|
|
||||||
* @return bool Returns true if the account has the permission, false otherwise
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function hasPermission(int $permission, int $unit = null, string $app = null, int $module = null, int $type = null, $element = null, $component = null) : bool
|
|
||||||
{
|
|
||||||
$app = isset($app) ? strtolower($app) : $app;
|
|
||||||
|
|
||||||
foreach ($this->permissions as $p) {
|
foreach ($this->permissions as $p) {
|
||||||
if (($p->getUnit() === $unit || $p->getUnit() === null || !isset($unit))
|
if ($p->hasPermission($permission, $unit, $app, $module, $category, $element, $component)) {
|
||||||
&& ($p->getApp() === $app || $p->getApp() === null || !isset($app))
|
|
||||||
&& ($p->getModule() === $module || $p->getModule() === null || !isset($module))
|
|
||||||
&& ($p->getType() === $type || $p->getType() === null || !isset($type))
|
|
||||||
&& ($p->getElement() === $element || $p->getElement() === null || !isset($element))
|
|
||||||
&& ($p->getComponent() === $component || $p->getComponent() === null || !isset($component))
|
|
||||||
&& ($p->getPermission() | $permission) === $p->getPermission()
|
|
||||||
) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -339,101 +187,109 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get name.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @return string
|
* The constructor automatically sets the created date as well as the last activity to now.
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @param int $id Account id
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getName() : string
|
public function __construct(int $id = 0)
|
||||||
{
|
{
|
||||||
return $this->login;
|
$this->createdAt = new \DateTimeImmutable('now');
|
||||||
|
$this->lastActive = new \DateTime('now');
|
||||||
|
$this->id = $id;
|
||||||
|
$this->l11n = new Localization();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get name1.
|
* Get account id.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return int Account id
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getName1() : string
|
public function getId() : int
|
||||||
{
|
{
|
||||||
return $this->name1;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set name1.
|
* Get groups.
|
||||||
*
|
*
|
||||||
* @param string $name Name
|
* Every account can belong to multiple groups.
|
||||||
|
* These groups usually are used for permissions and categorize accounts.
|
||||||
|
*
|
||||||
|
* @return Group[] Returns array of all groups
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getGroups() : array
|
||||||
|
{
|
||||||
|
return $this->groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ids of groups
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getGroupIds() : array
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
$ids = [];
|
||||||
|
foreach ($this->groups as $group) {
|
||||||
|
$ids[] = $group->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ids;
|
||||||
|
*/
|
||||||
|
return \array_keys($this->groups);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add group.
|
||||||
|
*
|
||||||
|
* @param Group $group Group to add
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function setName1(string $name) /* : void */
|
public function addGroup(Group $group) : void
|
||||||
{
|
{
|
||||||
$this->name1 = $name;
|
$this->groups[] = $group;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get name2.
|
* User has group.
|
||||||
*
|
*
|
||||||
* @return string
|
* @param int $id Group id
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getName2() : string
|
public function hasGroup(int $id) : bool
|
||||||
{
|
{
|
||||||
return $this->name2;
|
foreach ($this->groups as $group) {
|
||||||
}
|
if ($group->id === $id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
return false;
|
||||||
* Set name2.
|
|
||||||
*
|
|
||||||
* @param string $name Name
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setName2(string $name) /* : void */
|
|
||||||
{
|
|
||||||
$this->name2 = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get name3.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getName3() : string
|
|
||||||
{
|
|
||||||
return $this->name3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set name3.
|
|
||||||
*
|
|
||||||
* @param string $name Name
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setName3(string $name) /* : void */
|
|
||||||
{
|
|
||||||
$this->name3 = $name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get email.
|
* Get email.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string Returns the email address
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getEmail() : string
|
public function getEmail() : string
|
||||||
{
|
{
|
||||||
|
|
@ -449,103 +305,27 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
*
|
*
|
||||||
* @throws \InvalidArgumentException Exception is thrown if the provided string is not a valid email
|
* @throws \InvalidArgumentException Exception is thrown if the provided string is not a valid email
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function setEmail(string $email) /* : void */
|
public function setEmail(string $email) : void
|
||||||
{
|
{
|
||||||
if (!Email::isValid($email)) {
|
if ($email !== '' && !Email::isValid($email)) {
|
||||||
throw new \InvalidArgumentException();
|
throw new \InvalidArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->email = mb_strtolower($email);
|
$this->email = \mb_strtolower($email);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get status.
|
|
||||||
*
|
|
||||||
* AccountStatus
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getStatus() : int
|
|
||||||
{
|
|
||||||
return $this->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get status.
|
|
||||||
*
|
|
||||||
* @param int $status Status
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setStatus(int $status) /* : void */
|
|
||||||
{
|
|
||||||
if (!AccountStatus::isValidValue($status)) {
|
|
||||||
throw new \InvalidArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->status = $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get type.
|
|
||||||
*
|
|
||||||
* AccountType
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getType() : int
|
|
||||||
{
|
|
||||||
return $this->type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get type.
|
|
||||||
*
|
|
||||||
* @param int $type Type
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setType(int $type) /* : void */
|
|
||||||
{
|
|
||||||
if (!AccountType::isValidValue($type)) {
|
|
||||||
throw new \InvalidArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->type = $type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get last activity.
|
* Get last activity.
|
||||||
*
|
*
|
||||||
* @return \DateTime
|
* @return \DateTimeInterface
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getLastActive() : \DateTime
|
public function getLastActive() : \DateTimeInterface
|
||||||
{
|
{
|
||||||
return $this->lastActive ?? $this->getCreatedAt();
|
return $this->lastActive ?? $this->createdAt;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get created at.
|
|
||||||
*
|
|
||||||
* @return \DateTime
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getCreatedAt() : \DateTime
|
|
||||||
{
|
|
||||||
return $this->createdAt ?? new \DateTime('NOW');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -555,31 +335,19 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*
|
*
|
||||||
* @throws \Exception
|
* @throws \Exception Throws this exception if the password_hash function fails
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function generatePassword(string $password) /* : void */
|
public function generatePassword(string $password) : void
|
||||||
{
|
{
|
||||||
$this->password = \password_hash($password, \PASSWORD_DEFAULT);
|
$temp = \password_hash($password, \PASSWORD_BCRYPT);
|
||||||
|
|
||||||
if ($this->password === false) {
|
if ($temp === false) {
|
||||||
throw new \Exception();
|
throw new \Exception('Internal password_hash error.'); // @codeCoverageIgnore
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$this->password = $temp;
|
||||||
* Set name.
|
|
||||||
*
|
|
||||||
* @param string $name Name
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setName(string $name) /* : void */
|
|
||||||
{
|
|
||||||
$this->login = $name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -587,23 +355,23 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function updateLastActive() /* : void */
|
public function updateLastActive() : void
|
||||||
{
|
{
|
||||||
$this->lastActive = new \DateTime('NOW');
|
$this->lastActive = new \DateTime('now');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get string representation.
|
* Get string representation.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string Returns the json_encode of this object
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString() : string
|
||||||
{
|
{
|
||||||
return json_encode($this->toArray());
|
return (string) \json_encode($this->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -612,8 +380,8 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
public function toArray() : array
|
public function toArray() : array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'name' => [
|
'name' => [
|
||||||
$this->name1,
|
$this->name1,
|
||||||
$this->name2,
|
$this->name2,
|
||||||
$this->name3,
|
$this->name3,
|
||||||
|
|
@ -628,13 +396,9 @@ class Account implements ArrayableInterface, \JsonSerializable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Json serialize.
|
* {@inheritdoc}
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public function jsonSerialize()
|
public function jsonSerialize() : mixed
|
||||||
{
|
{
|
||||||
return $this->toArray();
|
return $this->toArray();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
Account/AccountManager.php
Normal file → Executable file
61
Account/AccountManager.php
Normal file → Executable file
|
|
@ -1,16 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
|
|
@ -20,23 +20,22 @@ use phpOMS\DataStorage\Session\SessionInterface;
|
||||||
/**
|
/**
|
||||||
* Account manager class.
|
* Account manager class.
|
||||||
*
|
*
|
||||||
* The account manager is used to manage multiple accounts.
|
* The account manager is used to manage accounts.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
class AccountManager implements \Countable
|
final class AccountManager implements \Countable
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accounts.
|
* Accounts.
|
||||||
*
|
*
|
||||||
* @var Account[]
|
* @var Account[]
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
private $accounts = [];
|
private array $accounts = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Session.
|
* Session.
|
||||||
|
|
@ -44,14 +43,14 @@ class AccountManager implements \Countable
|
||||||
* @var SessionInterface
|
* @var SessionInterface
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
private $session = null;
|
private SessionInterface $session;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param SessionInterface $session Session
|
* @param SessionInterface $session Session
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function __construct(SessionInterface $session)
|
public function __construct(SessionInterface $session)
|
||||||
{
|
{
|
||||||
|
|
@ -65,15 +64,15 @@ class AccountManager implements \Countable
|
||||||
*
|
*
|
||||||
* @return Account
|
* @return Account
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function get(int $id = 0) : Account
|
public function get(int $id = 0) : Account
|
||||||
{
|
{
|
||||||
if ($id === 0) {
|
if ($id === 0) {
|
||||||
$account = new Account(Auth::authenticate($this->session));
|
$account = new Account(Auth::authenticate($this->session));
|
||||||
|
|
||||||
if (!isset($this->accounts[$account->getId()])) {
|
if (!isset($this->accounts[$account->id])) {
|
||||||
$this->accounts[$account->getId()] = $account;
|
$this->accounts[$account->id] = $account;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $account;
|
return $account;
|
||||||
|
|
@ -87,14 +86,14 @@ class AccountManager implements \Countable
|
||||||
*
|
*
|
||||||
* @param Account $account Account
|
* @param Account $account Account
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool Returns true if the account could be added otherwise false is returned
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function add(Account $account) : bool
|
public function add(Account $account) : bool
|
||||||
{
|
{
|
||||||
if (!isset($this->accounts[$account->getId()])) {
|
if (!isset($this->accounts[$account->id])) {
|
||||||
$this->accounts[$account->getId()] = $account;
|
$this->accounts[$account->id] = $account;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -107,9 +106,9 @@ class AccountManager implements \Countable
|
||||||
*
|
*
|
||||||
* @param int $id Account id
|
* @param int $id Account id
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool Returns true if the account could be removed otherwise false
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function remove(int $id) : bool
|
public function remove(int $id) : bool
|
||||||
{
|
{
|
||||||
|
|
@ -125,12 +124,12 @@ class AccountManager implements \Countable
|
||||||
/**
|
/**
|
||||||
* Get accounts count.
|
* Get accounts count.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int Returns the amount of accounts in the manager (>= 0)
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function count() : int
|
public function count() : int
|
||||||
{
|
{
|
||||||
return count($this->accounts);
|
return \count($this->accounts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
Account/AccountStatus.php
Normal file → Executable file
35
Account/AccountStatus.php
Normal file → Executable file
|
|
@ -1,16 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
|
|
@ -19,15 +19,18 @@ use phpOMS\Stdlib\Base\Enum;
|
||||||
/**
|
/**
|
||||||
* Account status enum.
|
* Account status enum.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
abstract class AccountStatus extends Enum
|
abstract class AccountStatus extends Enum
|
||||||
{
|
{
|
||||||
/* public */ const ACTIVE = 1;
|
public const ACTIVE = 1;
|
||||||
/* public */ const INACTIVE = 2;
|
|
||||||
/* public */ const TIMEOUT = 3;
|
public const INACTIVE = 2;
|
||||||
/* public */ const BANNED = 4;
|
|
||||||
|
public const TIMEOUT = 3;
|
||||||
|
|
||||||
|
public const BANNED = 4;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
Account/AccountType.php
Normal file → Executable file
29
Account/AccountType.php
Normal file → Executable file
|
|
@ -1,16 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
|
|
@ -19,13 +19,14 @@ use phpOMS\Stdlib\Base\Enum;
|
||||||
/**
|
/**
|
||||||
* Account type enum.
|
* Account type enum.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
abstract class AccountType extends Enum
|
abstract class AccountType extends Enum
|
||||||
{
|
{
|
||||||
/* public */ const USER = 0;
|
public const USER = 0;
|
||||||
/* public */ const GROUP = 1;
|
|
||||||
|
public const GROUP = 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
174
Account/Group.php
Normal file → Executable file
174
Account/Group.php
Normal file → Executable file
|
|
@ -1,63 +1,60 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
use phpOMS\Contract\ArrayableInterface;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account group class.
|
* Account group class.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
class Group implements ArrayableInterface, \JsonSerializable
|
class Group implements \JsonSerializable
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account id.
|
* Group id.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $id = 0;
|
public int $id = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account name.
|
* Group name.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $name = '';
|
public string $name = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account name.
|
* Group name.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $description = '';
|
public string $description = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Account name.
|
* Group members.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var array
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $members = [];
|
public array $members = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parents.
|
* Parents.
|
||||||
|
|
@ -65,7 +62,7 @@ class Group implements ArrayableInterface, \JsonSerializable
|
||||||
* @var int[]
|
* @var int[]
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $parents = [];
|
public array $parents = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Group status.
|
* Group status.
|
||||||
|
|
@ -73,131 +70,32 @@ class Group implements ArrayableInterface, \JsonSerializable
|
||||||
* @var int
|
* @var int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $status = GroupStatus::INACTIVE;
|
public int $status = GroupStatus::INACTIVE;
|
||||||
|
|
||||||
/**
|
use PermissionHandlingTrait;
|
||||||
* Permissions.
|
|
||||||
*
|
|
||||||
* @var int[]
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
protected $permissions = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get group id.
|
* Get group id.
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int Returns the id of the group
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getId() : int
|
public function getId() : int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get group name.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getName() : string
|
|
||||||
{
|
|
||||||
return $this->name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set group name.
|
|
||||||
*
|
|
||||||
* @param string $name Group name
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setName(string $name) /* : void */
|
|
||||||
{
|
|
||||||
$this->name = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get group description.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getDescription() : string
|
|
||||||
{
|
|
||||||
return $this->description;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set group description.
|
|
||||||
*
|
|
||||||
* @param string $description Group description
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setDescription(string $description) /* : void */
|
|
||||||
{
|
|
||||||
$this->description = $description;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get group status.
|
|
||||||
*
|
|
||||||
* @return int Group status
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getStatus() : int
|
|
||||||
{
|
|
||||||
return $this->status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set group status.
|
|
||||||
*
|
|
||||||
* @param int $status Group status
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @throws InvalidEnumValue
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setStatus(int $status) /* : void */
|
|
||||||
{
|
|
||||||
if (!GroupStatus::isValidValue($status)) {
|
|
||||||
throw new InvalidEnumValue($status);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->status = $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get string representation.
|
* Get string representation.
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string Returns the json_encode of this object
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function __toString()
|
public function __toString() : string
|
||||||
{
|
{
|
||||||
return json_encode($this->toArray());
|
return (string) \json_encode($this->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -217,11 +115,15 @@ class Group implements ArrayableInterface, \JsonSerializable
|
||||||
/**
|
/**
|
||||||
* Json serialize.
|
* Json serialize.
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array<string, mixed>
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function jsonSerialize()
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function jsonSerialize() : mixed
|
||||||
{
|
{
|
||||||
return $this->toArray();
|
return $this->toArray();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
Account/GroupStatus.php
Normal file → Executable file
34
Account/GroupStatus.php
Normal file → Executable file
|
|
@ -1,32 +1,34 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
use phpOMS\Stdlib\Base\Enum;
|
use phpOMS\Stdlib\Base\Enum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accept status enum.
|
* Group status enum.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
abstract class GroupStatus extends Enum
|
abstract class GroupStatus extends Enum
|
||||||
{
|
{
|
||||||
/* public */ const ACTIVE = 1;
|
public const ACTIVE = 1;
|
||||||
/* public */ const INACTIVE = 2;
|
|
||||||
/* public */ const HIDDEN = 4;
|
public const INACTIVE = 2;
|
||||||
|
|
||||||
|
public const HIDDEN = 4;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
45
Account/NullAccount.php
Normal file → Executable file
45
Account/NullAccount.php
Normal file → Executable file
|
|
@ -1,27 +1,46 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Null account class.
|
* Null account class.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
class NullAccount extends Account
|
final class NullAccount extends Account
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param int $id Model id
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(int $id = 0)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function jsonSerialize() : mixed
|
||||||
|
{
|
||||||
|
return ['id' => $this->id];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
46
Account/NullGroup.php
Executable file
46
Account/NullGroup.php
Executable file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Account
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null group class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Account
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class NullGroup extends Group
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param int $id Model id
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(int $id = 0)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function jsonSerialize() : mixed
|
||||||
|
{
|
||||||
|
return ['id' => $this->id];
|
||||||
|
}
|
||||||
|
}
|
||||||
429
Account/PermissionAbstract.php
Normal file → Executable file
429
Account/PermissionAbstract.php
Normal file → Executable file
|
|
@ -1,16 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
|
|
@ -20,12 +20,12 @@ namespace phpOMS\Account;
|
||||||
* This permission abstract is the basis for all permissions. Contrary to it's name it is not an
|
* This permission abstract is the basis for all permissions. Contrary to it's name it is not an
|
||||||
* abstract class and can be used directly if needed.
|
* abstract class and can be used directly if needed.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
class PermissionAbstract
|
class PermissionAbstract implements \JsonSerializable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Permission id.
|
* Permission id.
|
||||||
|
|
@ -33,276 +33,198 @@ class PermissionAbstract
|
||||||
* @var int
|
* @var int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $id = 0;
|
public int $id = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit id.
|
* Unit id.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var null|int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $unit = null;
|
public ?int $unit = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App name.
|
* App name.
|
||||||
*
|
*
|
||||||
* @var string
|
* @var null|int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $app = null;
|
public ?int $app = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module id.
|
* Module id.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var null|string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $module = null;
|
public ?string $module = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Providing module id.
|
* Providing module id.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var string
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $from = 0;
|
public ?string $from = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type.
|
* Type.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var null|int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $type = null;
|
public ?int $category = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Element id.
|
* Element id.
|
||||||
*
|
*
|
||||||
* @var int
|
* null === all
|
||||||
|
* int === specific
|
||||||
|
*
|
||||||
|
* @var null|int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $element = null;
|
public ?int $element = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component id.
|
* Component id.
|
||||||
*
|
*
|
||||||
* @var int
|
* null === all
|
||||||
|
* int === specific
|
||||||
|
* 0 === own data
|
||||||
|
*
|
||||||
|
* @var null|int
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $component = null;
|
public ?int $component = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permission.
|
* Permission.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var bool
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
protected $permission = PermissionType::NONE;
|
public bool $hasRead = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get permission id.
|
* Permission.
|
||||||
*
|
*
|
||||||
* @return int
|
* @var bool
|
||||||
*
|
* @since 1.0.0
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public function getId() : int
|
public bool $hasModify = false;
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get unit id.
|
* Permission.
|
||||||
*
|
*
|
||||||
* @return int
|
* @var bool
|
||||||
*
|
* @since 1.0.0
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public function getUnit() /* : ?int */
|
public bool $hasCreate = false;
|
||||||
{
|
|
||||||
return $this->unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set unit id.
|
* Default create permissions
|
||||||
*
|
*
|
||||||
* @param int $unit Unit
|
* @var null|string
|
||||||
*
|
* @since 1.0.0
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public function setUnit(int $unit = null) /* : void */
|
public ?string $defaultCPermissions = null;
|
||||||
{
|
|
||||||
$this->unit = $unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get app name.
|
* Permission.
|
||||||
*
|
*
|
||||||
* @return string
|
* @var bool
|
||||||
*
|
* @since 1.0.0
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public function getApp() /* : ?string */
|
public bool $hasDelete = false;
|
||||||
{
|
|
||||||
return $this->app;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set app name.
|
* Permission.
|
||||||
*
|
*
|
||||||
* @param string $app App name
|
* @var bool
|
||||||
*
|
* @since 1.0.0
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public function setApp(string $app = null) /* : void */
|
public bool $hasPermission = false;
|
||||||
{
|
|
||||||
$this->app = $app;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get module id.
|
* Default permission permissions
|
||||||
*
|
*
|
||||||
* @return int
|
* @var null|string
|
||||||
*
|
* @since 1.0.0
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public function getModule() /* : ?int */
|
public ?string $defaultPPermissions = null;
|
||||||
{
|
|
||||||
return $this->module;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set module id.
|
* Constructor.
|
||||||
*
|
*
|
||||||
* @param int $module Module
|
* @param null|int $unit Unit to check (null if all are acceptable)
|
||||||
|
* @param null|int $app App to check (null if all are acceptable)
|
||||||
|
* @param null|string $module Module Module to check (null if all are acceptable)
|
||||||
|
* @param null|string $from Provided by which module
|
||||||
|
* @param null|int $category Category (e.g. customer) (null if all are acceptable)
|
||||||
|
* @param null|int $element (e.g. customer id) (null if all are acceptable)
|
||||||
|
* @param null|int $component (e.g. address) (null if all are acceptable)
|
||||||
|
* @param int $permission Permission to check
|
||||||
*
|
*
|
||||||
* @return void
|
* @since 1.0.0
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
*/
|
||||||
public function setModule(int $module = null) /* : void */
|
public function __construct(
|
||||||
{
|
?int $unit = null,
|
||||||
$this->module = $module;
|
?int $app = null,
|
||||||
}
|
?string $module = null,
|
||||||
|
?string $from = null,
|
||||||
/**
|
?int $category = null,
|
||||||
* Get providing module id.
|
?int $element = null,
|
||||||
*
|
?int $component = null,
|
||||||
* @return int
|
int $permission = PermissionType::NONE
|
||||||
*
|
) {
|
||||||
* @since 1.0.0
|
$this->unit = $unit;
|
||||||
*/
|
$this->app = $app;
|
||||||
public function getFrom() /* : ?int */
|
$this->module = $module;
|
||||||
{
|
$this->from = $from;
|
||||||
return $this->from;
|
$this->category = $category;
|
||||||
}
|
$this->element = $element;
|
||||||
|
|
||||||
/**
|
|
||||||
* Set providing module id.
|
|
||||||
*
|
|
||||||
* @param int $from Providing module
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setFrom(int $from = null) /* : void */
|
|
||||||
{
|
|
||||||
$this->from = $from;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get type.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getType() /* : ?int */
|
|
||||||
{
|
|
||||||
return $this->type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set type.
|
|
||||||
*
|
|
||||||
* @param int $type Type
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setType(int $type = null) /* : void */
|
|
||||||
{
|
|
||||||
$this->type = $type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get element id.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getElement() /* : ?int */
|
|
||||||
{
|
|
||||||
return $this->element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set element id.
|
|
||||||
*
|
|
||||||
* @param int $element Element id
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setElement(int $element = null) /* : void */
|
|
||||||
{
|
|
||||||
$this->element = $element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get component id.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function getComponent() /* : ?int */
|
|
||||||
{
|
|
||||||
return $this->component;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set component id.
|
|
||||||
*
|
|
||||||
* @param int $component Component
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*
|
|
||||||
* @since 1.0.0
|
|
||||||
*/
|
|
||||||
public function setComponent(int $component = null) /* : void */
|
|
||||||
{
|
|
||||||
$this->component = $component;
|
$this->component = $component;
|
||||||
|
|
||||||
|
$this->hasRead = ($permission & PermissionType::READ) === PermissionType::READ;
|
||||||
|
$this->hasCreate = ($permission & PermissionType::CREATE) === PermissionType::CREATE;
|
||||||
|
$this->hasModify = ($permission & PermissionType::MODIFY) === PermissionType::MODIFY;
|
||||||
|
$this->hasDelete = ($permission & PermissionType::DELETE) === PermissionType::DELETE;
|
||||||
|
$this->hasPermission = ($permission & PermissionType::PERMISSION) === PermissionType::PERMISSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get permission
|
* Get permission
|
||||||
*
|
*
|
||||||
* @return int
|
* @return int Returns the permission (PermissionType)
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function getPermission() : int
|
public function getPermission() : int
|
||||||
{
|
{
|
||||||
return $this->permission;
|
$permission = 0;
|
||||||
|
|
||||||
|
if ($this->hasRead) {
|
||||||
|
$permission |= PermissionType::READ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->hasCreate) {
|
||||||
|
$permission |= PermissionType::CREATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->hasModify) {
|
||||||
|
$permission |= PermissionType::MODIFY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->hasDelete) {
|
||||||
|
$permission |= PermissionType::DELETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->hasPermission) {
|
||||||
|
$permission |= PermissionType::PERMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $permission === 0 ? PermissionType::NONE : $permission;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -312,11 +234,15 @@ class PermissionAbstract
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function setPermission(int $permission = 0) /* : void */
|
public function setPermission(int $permission = 0) : void
|
||||||
{
|
{
|
||||||
$this->permission = $permission;
|
$this->hasRead = ($permission & PermissionType::READ) === PermissionType::READ;
|
||||||
|
$this->hasCreate = ($permission & PermissionType::CREATE) === PermissionType::CREATE;
|
||||||
|
$this->hasModify = ($permission & PermissionType::MODIFY) === PermissionType::MODIFY;
|
||||||
|
$this->hasDelete = ($permission & PermissionType::DELETE) === PermissionType::DELETE;
|
||||||
|
$this->hasPermission = ($permission & PermissionType::PERMISSION) === PermissionType::PERMISSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -326,11 +252,27 @@ class PermissionAbstract
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function addPermission(int $permission = 0) /* : void */
|
public function addPermission(int $permission = 0) : void
|
||||||
{
|
{
|
||||||
$this->permission |= $permission;
|
switch($permission) {
|
||||||
|
case PermissionType::READ:
|
||||||
|
$this->hasRead = true;
|
||||||
|
break;
|
||||||
|
case PermissionType::CREATE:
|
||||||
|
$this->hasCreate = true;
|
||||||
|
break;
|
||||||
|
case PermissionType::MODIFY:
|
||||||
|
$this->hasModify = true;
|
||||||
|
break;
|
||||||
|
case PermissionType::DELETE:
|
||||||
|
$this->hasDelete = true;
|
||||||
|
break;
|
||||||
|
case PermissionType::PERMISSION:
|
||||||
|
$this->hasPermission = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -338,12 +280,87 @@ class PermissionAbstract
|
||||||
*
|
*
|
||||||
* @param int $permission Permission
|
* @param int $permission Permission
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool Returns true if the permission is set otherwise returns false
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
public function hasPermission(int $permission) : bool
|
public function hasPermissionFlags(int $permission) : bool
|
||||||
{
|
{
|
||||||
return ($this->permission | $permission) === $this->permission;
|
return ($this->getPermission() & $permission) === $permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has permissions.
|
||||||
|
*
|
||||||
|
* Checks if the permission is defined
|
||||||
|
*
|
||||||
|
* @param int $permission Permission to check
|
||||||
|
* @param null|int $unit Unit Unit to check (null if all are acceptable)
|
||||||
|
* @param null|int $app App App to check (null if all are acceptable)
|
||||||
|
* @param null|string $module Module Module to check (null if all are acceptable)
|
||||||
|
* @param null|int $category Category (e.g. customer) (null if all are acceptable)
|
||||||
|
* @param null|int $element (e.g. customer id) (null if all are acceptable)
|
||||||
|
* @param null|int $component (e.g. address) (null if all are acceptable)
|
||||||
|
*
|
||||||
|
* @return bool Returns true if the permission is set, false otherwise
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function hasPermission(
|
||||||
|
int $permission,
|
||||||
|
?int $unit = null,
|
||||||
|
?int $app = null,
|
||||||
|
?string $module = null,
|
||||||
|
?int $category = null,
|
||||||
|
?int $element = null,
|
||||||
|
?int $component = null
|
||||||
|
) : bool
|
||||||
|
{
|
||||||
|
return $permission === PermissionType::NONE ||
|
||||||
|
(($unit === null || $this->unit === null || $this->unit === $unit)
|
||||||
|
&& ($app === null || $this->app === null || $this->app === $app)
|
||||||
|
&& ($module === null || $this->module === null || $this->module === $module)
|
||||||
|
&& ($category === null || $this->category === null || $this->category === $category)
|
||||||
|
&& ($element === null || $this->element === null || $this->element === $element)
|
||||||
|
&& ($component === null || $this->component === null || $this->component === $component)
|
||||||
|
&& ($this->getPermission() & $permission) === $permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is equals.
|
||||||
|
*
|
||||||
|
* @param self $permission Permission
|
||||||
|
*
|
||||||
|
* @return bool Returns true if the permission is the same
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isEqual(self $permission) : bool
|
||||||
|
{
|
||||||
|
return $this->unit === $permission->unit
|
||||||
|
&& $this->app === $permission->app
|
||||||
|
&& $this->module === $permission->module
|
||||||
|
&& $this->category === $permission->category
|
||||||
|
&& $this->element === $permission->element
|
||||||
|
&& $this->component === $permission->component
|
||||||
|
&& $this->getPermission() === $permission->getPermission();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function jsonSerialize() : mixed
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'unit' => $this->unit,
|
||||||
|
'app' => $this->app,
|
||||||
|
'module' => $this->module,
|
||||||
|
'from' => $this->from,
|
||||||
|
'category' => $this->category,
|
||||||
|
'element' => $this->element,
|
||||||
|
'component' => $this->component,
|
||||||
|
'permission' => $this->getPermission(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
154
Account/PermissionHandlingTrait.php
Executable file
154
Account/PermissionHandlingTrait.php
Executable file
|
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Account
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permission handling trait.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Account
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
trait PermissionHandlingTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Permissions.
|
||||||
|
*
|
||||||
|
* @var PermissionAbstract[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public array $permissions = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set permissions.
|
||||||
|
*
|
||||||
|
* The method accepts an array of permissions. All existing permissions are replaced.
|
||||||
|
*
|
||||||
|
* @param PermissionAbstract[] $permissions Permissions
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setPermissions(array $permissions) : void
|
||||||
|
{
|
||||||
|
$this->permissions = $permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add permissions.
|
||||||
|
*
|
||||||
|
* Adds permissions
|
||||||
|
*
|
||||||
|
* @param array<array|PermissionAbstract> $permissions Array of permissions to add
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function addPermissions(array $permissions) : void
|
||||||
|
{
|
||||||
|
foreach ($permissions as $permission) {
|
||||||
|
if (\is_array($permission)) {
|
||||||
|
$this->permissions = \array_merge($this->permissions, $permission);
|
||||||
|
} else {
|
||||||
|
$this->permissions[] = $permission;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add permission.
|
||||||
|
*
|
||||||
|
* Adds a single permission
|
||||||
|
*
|
||||||
|
* @param PermissionAbstract $permission Permission to add
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function addPermission(PermissionAbstract $permission) : void
|
||||||
|
{
|
||||||
|
$this->permissions[] = $permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove permission.
|
||||||
|
*
|
||||||
|
* @param PermissionAbstract $permission Permission to remove
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function removePermission(PermissionAbstract $permission) : void
|
||||||
|
{
|
||||||
|
foreach ($this->permissions as $key => $p) {
|
||||||
|
if ($p->isEqual($permission)) {
|
||||||
|
unset($this->permissions[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get permissions.
|
||||||
|
*
|
||||||
|
* @return PermissionAbstract[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getPermissions() : array
|
||||||
|
{
|
||||||
|
return $this->permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has permissions.
|
||||||
|
*
|
||||||
|
* Checks if the permission is defined
|
||||||
|
*
|
||||||
|
* @param int $permission Permission to check
|
||||||
|
* @param null|int $unit Unit Unit to check (null if all are acceptable)
|
||||||
|
* @param null|int $app App App to check (null if all are acceptable)
|
||||||
|
* @param null|string $module Module Module to check (null if all are acceptable)
|
||||||
|
* @param null|int $category Type (e.g. customer) (null if all are acceptable)
|
||||||
|
* @param null|int $element (e.g. customer id) (null if all are acceptable)
|
||||||
|
* @param null|int $component (e.g. address) (null if all are acceptable)
|
||||||
|
*
|
||||||
|
* @return bool Returns true if the permission is set, false otherwise
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function hasPermission(
|
||||||
|
int $permission,
|
||||||
|
?int $unit = null,
|
||||||
|
?int $app = null,
|
||||||
|
?string $module = null,
|
||||||
|
?int $category = null,
|
||||||
|
?int $element = null,
|
||||||
|
?int $component = null
|
||||||
|
) : bool
|
||||||
|
{
|
||||||
|
foreach ($this->permissions as $p) {
|
||||||
|
if ($p->hasPermission($permission, $unit, $app, $module, $category, $element, $component)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Account/PermissionOwner.php
Executable file
34
Account/PermissionOwner.php
Executable file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Account
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
|
use phpOMS\Stdlib\Base\Enum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permision type/owner enum.
|
||||||
|
*
|
||||||
|
* A permission can be long to a group or an account.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Account
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
abstract class PermissionOwner extends Enum
|
||||||
|
{
|
||||||
|
public const GROUP = 1;
|
||||||
|
|
||||||
|
public const ACCOUNT = 2;
|
||||||
|
}
|
||||||
41
Account/PermissionType.php
Normal file → Executable file
41
Account/PermissionType.php
Normal file → Executable file
|
|
@ -1,16 +1,16 @@
|
||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Orange Management
|
* Jingga
|
||||||
*
|
*
|
||||||
* PHP Version 7.1
|
* PHP Version 8.2
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @copyright Dennis Eichhorn
|
* @copyright Dennis Eichhorn
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
*/
|
*/
|
||||||
declare(strict_types = 1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace phpOMS\Account;
|
namespace phpOMS\Account;
|
||||||
|
|
||||||
|
|
@ -19,17 +19,22 @@ use phpOMS\Stdlib\Base\Enum;
|
||||||
/**
|
/**
|
||||||
* Permission type enum.
|
* Permission type enum.
|
||||||
*
|
*
|
||||||
* @package phpOMS\Account
|
* @package phpOMS\Account
|
||||||
* @license OMS License 1.0
|
* @license OMS License 2.0
|
||||||
* @link http://website.orange-management.de
|
* @link https://jingga.app
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
abstract class PermissionType extends Enum
|
abstract class PermissionType extends Enum
|
||||||
{
|
{
|
||||||
/* public */ const NONE = 1;
|
public const NONE = 1; // No permission
|
||||||
/* public */ const READ = 2;
|
|
||||||
/* public */ const CREATE = 4;
|
public const READ = 2; // Is able to read models/data
|
||||||
/* public */ const MODIFY = 8;
|
|
||||||
/* public */ const DELETE = 16;
|
public const CREATE = 4; // Is able to create models/data
|
||||||
/* public */ const PERMISSION = 32;
|
|
||||||
|
public const MODIFY = 8; // Is able to modify models/data
|
||||||
|
|
||||||
|
public const DELETE = 16; // Is able to delete models/data
|
||||||
|
|
||||||
|
public const PERMISSION = 32; // Is able to change permissions
|
||||||
}
|
}
|
||||||
|
|
|
||||||
102
Ai/NeuralNetwork/Neuron.php
Executable file
102
Ai/NeuralNetwork/Neuron.php
Executable file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Ai\NeuralNetwork
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Ai\NeuralNetwork;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Neuron
|
||||||
|
*
|
||||||
|
* @package phpOMS\Ai\NeuralNetwork
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Neuron
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Neuron inputs
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $inputs = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input weights
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $weights = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bias
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public float $bias = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param array $inputs Neuron inputs/connections
|
||||||
|
* @param array $weights Input weights
|
||||||
|
* @param float $bias Input bias
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(array $inputs = [], array $weights = [], float $bias = 0.0)
|
||||||
|
{
|
||||||
|
$this->inputs = $inputs;
|
||||||
|
$this->weights = $weights;
|
||||||
|
$this->bias = $bias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add neuron input
|
||||||
|
*
|
||||||
|
* @param mixed $input Input
|
||||||
|
* @param float $weight Weight of input
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function addInput(mixed $input, float $weight) : void
|
||||||
|
{
|
||||||
|
$this->inputs[] = $input;
|
||||||
|
$this->weights[] = $weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create node output
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function output() : float
|
||||||
|
{
|
||||||
|
$length = \count($this->inputs);
|
||||||
|
$output = 0.0;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; ++$i) {
|
||||||
|
$output += $this->inputs[$i]->output() * $this->weights[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output + $this->bias;
|
||||||
|
// return $this->activationFunction($output + $this->bias);
|
||||||
|
}
|
||||||
|
}
|
||||||
363
Ai/Ocr/BasicOcr.php
Executable file
363
Ai/Ocr/BasicOcr.php
Executable file
|
|
@ -0,0 +1,363 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Ai\Ocr
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Ai\Ocr;
|
||||||
|
|
||||||
|
use phpOMS\Math\Topology\MetricsND;
|
||||||
|
use phpOMS\System\File\PathException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic OCR implementation for MNIST data
|
||||||
|
*
|
||||||
|
* @package phpOMS\Ai\Ocr
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class BasicOcr
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Dataset on which the OCR is trained on.
|
||||||
|
*
|
||||||
|
* The data needs to be MNIST data.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $Xtrain = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resultset on which the OCR is trained on.
|
||||||
|
*
|
||||||
|
* These are the actual values for the Xtrain data and must therefore have the same dimension.
|
||||||
|
*
|
||||||
|
* The labels need to be MNIST labels.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $ytrain = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Train OCR with data and result/labels
|
||||||
|
*
|
||||||
|
* @param string $dataPath Impage path to read
|
||||||
|
* @param string $labelPath Label path to read
|
||||||
|
* @param int $limit Limit (0 = unlimited)
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function trainWith(string $dataPath, string $labelPath, int $limit = 0) : void
|
||||||
|
{
|
||||||
|
$Xtrain = $this->readImages($dataPath, $limit);
|
||||||
|
$ytrain = $this->readLabels($labelPath, $limit);
|
||||||
|
|
||||||
|
$this->Xtrain = \array_merge($this->Xtrain, $Xtrain);
|
||||||
|
$this->ytrain = \array_merge($this->ytrain, $ytrain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read image from path
|
||||||
|
*
|
||||||
|
* @param string $path Image to read
|
||||||
|
* @param int $limit Limit
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @throws PathException
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function readImages(string $path, int $limit = 0) : array
|
||||||
|
{
|
||||||
|
if (!\is_file($path)) {
|
||||||
|
throw new PathException($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fp = \fopen($path, 'r');
|
||||||
|
if ($fp === false) {
|
||||||
|
throw new PathException($path); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) {
|
||||||
|
return []; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
// $magicNumber = $unpack[1];
|
||||||
|
// 2051 === image data (should always be this)
|
||||||
|
// 2049 === label data
|
||||||
|
|
||||||
|
if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) {
|
||||||
|
return []; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
$numberOfImages = $unpack[1];
|
||||||
|
|
||||||
|
if ($limit > 0) {
|
||||||
|
$numberOfImages = \min($numberOfImages, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) {
|
||||||
|
return []; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var int<0, max> $numberOfRows */
|
||||||
|
$numberOfRows = (int) $unpack[1];
|
||||||
|
|
||||||
|
if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) {
|
||||||
|
return []; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var int<0, max> $numberOfColumns */
|
||||||
|
$numberOfColumns = (int) $unpack[1];
|
||||||
|
|
||||||
|
$images = [];
|
||||||
|
for ($i = 0; $i < $numberOfImages; ++$i) {
|
||||||
|
if (($read = \fread($fp, $numberOfRows * $numberOfColumns)) === false
|
||||||
|
|| ($unpack = \unpack('C*', $read)) === false
|
||||||
|
) {
|
||||||
|
return []; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
$images[] = \array_values($unpack);
|
||||||
|
}
|
||||||
|
|
||||||
|
\fclose($fp);
|
||||||
|
|
||||||
|
return $images;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read labels from from path
|
||||||
|
*
|
||||||
|
* @param string $path Labels path
|
||||||
|
* @param int $limit Limit
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @throws PathException
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function readLabels(string $path, int $limit = 0) : array
|
||||||
|
{
|
||||||
|
if (!\is_file($path)) {
|
||||||
|
throw new PathException($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
$fp = \fopen($path, 'r');
|
||||||
|
if ($fp === false) {
|
||||||
|
throw new PathException($path); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) {
|
||||||
|
return []; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
// $magicNumber = $unpack[1];
|
||||||
|
// 2051 === image data
|
||||||
|
// 2049 === label data (should always be this)
|
||||||
|
|
||||||
|
if (($read = \fread($fp, 4)) === false || ($unpack = \unpack('N', $read)) === false) {
|
||||||
|
return []; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
$numberOfLabels = $unpack[1];
|
||||||
|
|
||||||
|
if ($limit > 0) {
|
||||||
|
$numberOfLabels = \min($numberOfLabels, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
$labels = [];
|
||||||
|
for ($i = 0; $i < $numberOfLabels; ++$i) {
|
||||||
|
if (($read = \fread($fp, 1)) === false || ($unpack = \unpack('C', $read)) === false) {
|
||||||
|
return []; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
$labels[] = $unpack[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
\fclose($fp);
|
||||||
|
|
||||||
|
return $labels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the k-nearest matches for test data
|
||||||
|
*
|
||||||
|
* @param array $Xtrain Image data used for training
|
||||||
|
* @param array $ytrain Labels associated with the trained data
|
||||||
|
* @param array $Xtest Image data from the image to categorize
|
||||||
|
* @param int $k Amount of best fits that should be found
|
||||||
|
*/
|
||||||
|
private function kNearest(array $Xtrain, array $ytrain, array $Xtest, int $k = 3) : array
|
||||||
|
{
|
||||||
|
$predictedLabels = [];
|
||||||
|
foreach ($Xtest as $sample) {
|
||||||
|
$distances = $this->getDistances($Xtrain, $sample);
|
||||||
|
\asort($distances);
|
||||||
|
|
||||||
|
$keys = \array_keys($distances);
|
||||||
|
|
||||||
|
$candidateLabels = [];
|
||||||
|
for ($i = 0; $i < $k; ++$i) {
|
||||||
|
$candidateLabels[] = $ytrain[$keys[$i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// find best match
|
||||||
|
$countedCandidates = \array_count_values($candidateLabels);
|
||||||
|
|
||||||
|
foreach ($candidateLabels as $i => $label) {
|
||||||
|
$predictedLabels[] = [
|
||||||
|
'label' => $label,
|
||||||
|
'prob' => $countedCandidates[$label] / $k,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $predictedLabels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fitting method in order to see how similar two datasets are.
|
||||||
|
*
|
||||||
|
* @param array $Xtrain Image data used for training
|
||||||
|
* @param array $sample Image data to compare against
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function getDistances(array $Xtrain, array $sample) : array
|
||||||
|
{
|
||||||
|
$dist = [];
|
||||||
|
foreach ($Xtrain as $train) {
|
||||||
|
$dist[] = MetricsND::euclidean($train, $sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create MNIST file from images
|
||||||
|
*
|
||||||
|
* @param string[] $images Images
|
||||||
|
* @param string $out Output file
|
||||||
|
* @param int $resolution Resolution of the iomages
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function imagesToMNIST(array $images, string $out, int $resolution) : void
|
||||||
|
{
|
||||||
|
$out = \fopen($out, 'wb');
|
||||||
|
if ($out === false) {
|
||||||
|
return; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
\fwrite($out, \pack('N', 2051));
|
||||||
|
\fwrite($out, \pack('N', \count($images)));
|
||||||
|
\fwrite($out, \pack('N', $resolution));
|
||||||
|
\fwrite($out, \pack('N', $resolution));
|
||||||
|
|
||||||
|
$size = $resolution * $resolution;
|
||||||
|
|
||||||
|
foreach ($images as $in) {
|
||||||
|
$inString = \file_get_contents($in);
|
||||||
|
if ($inString === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$im = \imagecreatefromstring($inString);
|
||||||
|
if ($im === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new = \imagescale($im, $resolution, $resolution);
|
||||||
|
if ($new === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the image to grayscale and normalize the pixel values
|
||||||
|
$mnist = [];
|
||||||
|
for ($i = 0; $i < $resolution; ++$i) {
|
||||||
|
for ($j = 0; $j < $resolution; ++$j) {
|
||||||
|
$pixel = \imagecolorat($new, $j, $i);
|
||||||
|
$gray = \round(
|
||||||
|
(
|
||||||
|
0.299 * (($pixel >> 16) & 0xFF)
|
||||||
|
+ 0.587 * (($pixel >> 8) & 0xFF)
|
||||||
|
+ 0.114 * ($pixel & 0xFF)
|
||||||
|
) / 255,
|
||||||
|
3
|
||||||
|
);
|
||||||
|
|
||||||
|
$mnist[] = $gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < $size; ++$i) {
|
||||||
|
\fwrite($out, \pack('C', (int) \round($mnist[$i] * 255)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
\fclose($out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert labels to MNIST format
|
||||||
|
*
|
||||||
|
* @param string[] $data Labels (one char per label)
|
||||||
|
* @param string $out Output path
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function labelsToMNIST(array $data, string $out) : void
|
||||||
|
{
|
||||||
|
// Only allows single char labels
|
||||||
|
$out = \fopen($out, 'wb');
|
||||||
|
if ($out === false) {
|
||||||
|
return; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
\fwrite($out, \pack('N', 2049));
|
||||||
|
\fwrite($out, \pack('N', \count($data)));
|
||||||
|
|
||||||
|
foreach ($data as $e) {
|
||||||
|
\fwrite($out, \pack('C', $e));
|
||||||
|
}
|
||||||
|
|
||||||
|
\fclose($out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Categorize an unknown image
|
||||||
|
*
|
||||||
|
* @param string $path Path to the image to categorize/evaluate/match against the training data
|
||||||
|
* @param int $comparison Amount of comparisons
|
||||||
|
* @param int $limit Limit (0 = unlimited)
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function matchImage(string $path, int $comparison = 3, int $limit = 0) : array
|
||||||
|
{
|
||||||
|
$Xtest = $this->readImages($path, $limit);
|
||||||
|
|
||||||
|
return $this->kNearest($this->Xtrain, $this->ytrain, $Xtest, $comparison);
|
||||||
|
}
|
||||||
|
}
|
||||||
159
Ai/Ocr/Tesseract/TesseractOcr.php
Executable file
159
Ai/Ocr/Tesseract/TesseractOcr.php
Executable file
|
|
@ -0,0 +1,159 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Ai\Ocr\Tesseract
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Ai\Ocr\Tesseract;
|
||||||
|
|
||||||
|
use phpOMS\System\File\PathException;
|
||||||
|
use phpOMS\System\SystemUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tesseract api
|
||||||
|
*
|
||||||
|
* @package phpOMS\Ai\Ocr\Tesseract
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class TesseractOcr
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Tesseract path.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
protected static string $bin = '/usr/bin/tesseract';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set tesseract binary.
|
||||||
|
*
|
||||||
|
* @param string $path tesseract path
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @throws PathException This exception is thrown if the binary path doesn't exist
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function setBin(string $path) : void
|
||||||
|
{
|
||||||
|
if (\realpath($path) === false) {
|
||||||
|
throw new PathException($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$bin = \realpath($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prase image
|
||||||
|
*
|
||||||
|
* @param string $image Image path
|
||||||
|
* @param array $languages Languages to use
|
||||||
|
* @param int $psm Page segmentation mode (0 - 13)
|
||||||
|
* 0 Orientation and script detection (OSD) only.
|
||||||
|
* 1 Automatic page segmentation with OSD.
|
||||||
|
* 2 Automatic page segmentation, but no OSD, or OCR.
|
||||||
|
* 3 Fully automatic page segmentation, but no OSD. (Default)
|
||||||
|
* 4 Assume a single column of text of variable sizes.
|
||||||
|
* 5 Assume a single uniform block of vertically aligned text.
|
||||||
|
* 6 Assume a single uniform block of text.
|
||||||
|
* 7 Treat the image as a single text line.
|
||||||
|
* 8 Treat the image as a single word.
|
||||||
|
* 9 Treat the image as a single word in a circle.
|
||||||
|
* 10 Treat the image as a single character.
|
||||||
|
* 11 Sparse text. Find as much text as possible in no particular order.
|
||||||
|
* 12 Sparse text with OSD.
|
||||||
|
* 13 Raw line. Treat the image as a single text line, bypassing hacks that are Tesseract-specific.
|
||||||
|
* @param int $oem OCR engine modes
|
||||||
|
* 0 Legacy engine only.
|
||||||
|
* 1 Neural nets LSTM engine only.
|
||||||
|
* 2 Legacy + LSTM engines.
|
||||||
|
* 3 Default, based on what is available
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function parseImage(string $image, array $languages = ['eng', 'deu'], int $psm = 3, int $oem = 3) : string
|
||||||
|
{
|
||||||
|
$temp = \tempnam(\sys_get_temp_dir(), 'oms_ocr_');
|
||||||
|
if ($temp === false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$extension = 'png';
|
||||||
|
try {
|
||||||
|
// Tesseract needs higher dpi to work properly (identify + adjust if necessary)
|
||||||
|
$dpi = (int) \trim(\implode('', SystemUtils::runProc(
|
||||||
|
'identify',
|
||||||
|
'-quiet -format "%x" ' . $image
|
||||||
|
)));
|
||||||
|
|
||||||
|
if ($dpi < 300) {
|
||||||
|
$split = \explode('.', $image);
|
||||||
|
$extension = \end($split);
|
||||||
|
|
||||||
|
SystemUtils::runProc(
|
||||||
|
'convert',
|
||||||
|
'-units PixelsPerInch ' . $image . ' -resample 300 ' . $temp . '.' . $extension
|
||||||
|
);
|
||||||
|
|
||||||
|
$image = $temp . '.' . $extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do actual parsing
|
||||||
|
SystemUtils::runProc(
|
||||||
|
self::$bin,
|
||||||
|
$image . ' '
|
||||||
|
. $temp
|
||||||
|
. ' -c preserve_interword_spaces=1'
|
||||||
|
. ' --psm ' . $psm
|
||||||
|
. ' --oem ' . $oem
|
||||||
|
. (empty($languages) ? '' : ' -l ' . \implode('+', $languages))
|
||||||
|
);
|
||||||
|
} catch (\Throwable $_) {
|
||||||
|
if (\is_file($temp . '.' . $extension)) {
|
||||||
|
\unlink($temp . '.' . $extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\is_file($temp . '.' . $extension)) {
|
||||||
|
\unlink($temp . '.' . $extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
$filepath = \is_file($temp . '.txt')
|
||||||
|
? $temp . '.txt'
|
||||||
|
: $temp;
|
||||||
|
|
||||||
|
if (!\is_file($filepath)) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
\unlink($temp);
|
||||||
|
|
||||||
|
return '';
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = \file_get_contents($filepath);
|
||||||
|
if ($parsed === false) {
|
||||||
|
$parsed = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
\unlink($filepath);
|
||||||
|
\unlink($temp);
|
||||||
|
|
||||||
|
return \trim($parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Algorithm/Clustering/AffinityPropagation.php
Normal file
30
Algorithm/Clustering/AffinityPropagation.php
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering points
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see ./clustering_overview.png
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Implement
|
||||||
|
*/
|
||||||
|
final class AffinityPropagation
|
||||||
|
{
|
||||||
|
}
|
||||||
154
Algorithm/Clustering/AgglomerativeClustering.php
Normal file
154
Algorithm/Clustering/AgglomerativeClustering.php
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
use phpOMS\Math\Topology\MetricsND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering points
|
||||||
|
*
|
||||||
|
* The parent category of this clustering algorithm is hierarchical clustering.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license Base: MIT Copyright (c) 2020 Greene Laboratory
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see ./DivisiveClustering.php
|
||||||
|
* @see ./clustering_overview.png
|
||||||
|
* @see https://en.wikipedia.org/wiki/Hierarchical_clustering
|
||||||
|
* @see https://github.com/greenelab/hclust/blob/master/README.md
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Implement
|
||||||
|
* @todo Implement missing linkage functions
|
||||||
|
*/
|
||||||
|
final class AgglomerativeClustering implements ClusteringInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Metric to calculate the distance between two points
|
||||||
|
*
|
||||||
|
* @var \Closure
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public \Closure $metric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metric to calculate the distance between two points
|
||||||
|
*
|
||||||
|
* @var \Closure
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public \Closure $linkage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param null|\Closure $metric metric to use for the distance between two points
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(?\Closure $metric = null, ?\Closure $linkage = null)
|
||||||
|
{
|
||||||
|
$this->metric = $metric ?? function (Point $a, Point $b) {
|
||||||
|
$aCoordinates = $a->coordinates;
|
||||||
|
$bCoordinates = $b->coordinates;
|
||||||
|
|
||||||
|
return MetricsND::euclidean($aCoordinates, $bCoordinates);
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->linkage = $linkage ?? function (array $a, array $b, array $distances) {
|
||||||
|
return self::averageDistanceLinkage($a, $b, $distances);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum/Complete-Linkage clustering
|
||||||
|
*/
|
||||||
|
public static function maximumDistanceLinkage(array $setA, array $setB, array $distances) : float
|
||||||
|
{
|
||||||
|
$max = \PHP_INT_MIN;
|
||||||
|
foreach ($setA as $a) {
|
||||||
|
foreach ($setB as $b) {
|
||||||
|
if ($distances[$a][$b] > $max) {
|
||||||
|
$max = $distances[$a][$b];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum/Single-Linkage clustering
|
||||||
|
*/
|
||||||
|
public static function minimumDistanceLinkage(array $setA, array $setB, array $distances) : float
|
||||||
|
{
|
||||||
|
$min = \PHP_INT_MAX;
|
||||||
|
foreach ($setA as $a) {
|
||||||
|
foreach ($setB as $b) {
|
||||||
|
if ($distances[$a][$b] < $min) {
|
||||||
|
$min = $distances[$a][$b];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $min;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unweighted average linkage clustering (UPGMA)
|
||||||
|
*/
|
||||||
|
public static function averageDistanceLinkage(array $setA, array $setB, array $distances) : float
|
||||||
|
{
|
||||||
|
$distance = 0;
|
||||||
|
foreach ($setA as $a) {
|
||||||
|
$distance += \array_sum($distances[$a]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $distance / \count($setA) / \count($setB);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCentroids() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getClusters() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function cluster(Point $point) : ?Point
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getNoise() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
61
Algorithm/Clustering/Birch.php
Normal file
61
Algorithm/Clustering/Birch.php
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering points
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see ./clustering_overview.png
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Implement
|
||||||
|
*/
|
||||||
|
final class Birch implements ClusteringInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCentroids() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getClusters() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function cluster(Point $point) : ?Point
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getNoise() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
71
Algorithm/Clustering/ClusteringInterface.php
Normal file
71
Algorithm/Clustering/ClusteringInterface.php
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering interface.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
interface ClusteringInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get cluster centroids
|
||||||
|
*
|
||||||
|
* @return Point[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getCentroids() : array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cluster assignments of the training data
|
||||||
|
*
|
||||||
|
* @return Point[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getClusters() : array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cluster a single point
|
||||||
|
*
|
||||||
|
* This point doesn't have to be in the training data.
|
||||||
|
*
|
||||||
|
* @param Point $point Point to cluster
|
||||||
|
*
|
||||||
|
* @return null|Point
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function cluster(Point $point) : ?Point;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get noise data.
|
||||||
|
*
|
||||||
|
* Data points from the training data that are not part of a cluster.
|
||||||
|
*
|
||||||
|
* @return Point[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getNoise() : array;
|
||||||
|
|
||||||
|
// Not possible to interface due to different implementations
|
||||||
|
// public function generateClusters(...) : void
|
||||||
|
}
|
||||||
331
Algorithm/Clustering/DBSCAN.php
Normal file
331
Algorithm/Clustering/DBSCAN.php
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
use phpOMS\Math\Geometry\ConvexHull\MonotoneChain;
|
||||||
|
use phpOMS\Math\Geometry\Shape\D2\Polygon;
|
||||||
|
use phpOMS\Math\Topology\MetricsND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering points
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see ./clustering_overview.png
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Expand to n dimensions
|
||||||
|
*/
|
||||||
|
final class DBSCAN implements ClusteringInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Epsilon for float comparison.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public const EPSILON = 4.88e-04;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metric to calculate the distance between two points
|
||||||
|
*
|
||||||
|
* @var \Closure
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private \Closure $metric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Points outside of any cluster
|
||||||
|
*
|
||||||
|
* @var Point[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $noisePoints = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All points
|
||||||
|
*
|
||||||
|
* @var Point[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $points = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Points of the cluster centers
|
||||||
|
*
|
||||||
|
* @var Point[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $clusterCenters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clusters
|
||||||
|
*
|
||||||
|
* Array of points assigned to a cluster
|
||||||
|
*
|
||||||
|
* @var array<int, Point[]>
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $clusters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convex hull of all clusters
|
||||||
|
*
|
||||||
|
* @var array<array>
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $convexHulls = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cluster points
|
||||||
|
*
|
||||||
|
* Points in clusters (helper to avoid looping the cluster array)
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $clusteredPoints = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Distance matrix
|
||||||
|
*
|
||||||
|
* Distances between points
|
||||||
|
*
|
||||||
|
* @var array<float[]>
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $distanceMatrix = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param null|\Closure $metric metric to use for the distance between two points
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(?\Closure $metric = null)
|
||||||
|
{
|
||||||
|
$this->metric = $metric ?? function (Point $a, Point $b) {
|
||||||
|
$aCoordinates = $a->coordinates;
|
||||||
|
$bCoordinates = $b->coordinates;
|
||||||
|
|
||||||
|
return MetricsND::euclidean($aCoordinates, $bCoordinates);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand cluster with additional point and potential neighbors.
|
||||||
|
*
|
||||||
|
* @param Point $point Point to add to a cluster
|
||||||
|
* @param array $neighbors Neighbors of point
|
||||||
|
* @param int $c Cluster id
|
||||||
|
* @param float $epsilon Max distance
|
||||||
|
* @param int $minPoints Min amount of points required for a cluster
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function expandCluster(
|
||||||
|
Point $point,
|
||||||
|
array $neighbors,
|
||||||
|
int $c,
|
||||||
|
float $epsilon,
|
||||||
|
int $minPoints
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
$this->clusters[$c][] = $point;
|
||||||
|
$this->clusteredPoints[] = $point;
|
||||||
|
$nPoint = \reset($neighbors);
|
||||||
|
|
||||||
|
while ($nPoint) {
|
||||||
|
$neighbors2 = $this->findNeighbors($nPoint, $epsilon);
|
||||||
|
|
||||||
|
if (\count($neighbors2) >= $minPoints) {
|
||||||
|
foreach ($neighbors2 as $nPoint2) {
|
||||||
|
if (!isset($neighbors[$nPoint2->name])) {
|
||||||
|
$neighbors[$nPoint2->name] = $nPoint2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\in_array($nPoint->name, $this->clusteredPoints)) {
|
||||||
|
$this->clusters[$c][] = $nPoint;
|
||||||
|
$this->clusteredPoints[] = $nPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nPoint = \next($neighbors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find neighbors of a point
|
||||||
|
*
|
||||||
|
* @param Point $point Base point for potential neighbors
|
||||||
|
* @param float $epsilon Max distance to neighbor
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function findNeighbors(Point $point, float $epsilon) : array
|
||||||
|
{
|
||||||
|
$neighbors = [];
|
||||||
|
foreach ($this->points as $point2) {
|
||||||
|
if ($point->isEquals($point2)) {
|
||||||
|
$distance = isset($this->distanceMatrix[$point->name])
|
||||||
|
? $this->distanceMatrix[$point->name][$point2->name]
|
||||||
|
: $this->distanceMatrix[$point2->name][$point->name];
|
||||||
|
|
||||||
|
if ($distance < $epsilon) {
|
||||||
|
$neighbors[$point2->name] = $point2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate distances between points
|
||||||
|
*
|
||||||
|
* @param array $points Array of all points
|
||||||
|
*
|
||||||
|
* @return float[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function generateDistanceMatrix(array $points) : array
|
||||||
|
{
|
||||||
|
$distances = [];
|
||||||
|
foreach ($points as $point) {
|
||||||
|
$distances[$point->name] = [];
|
||||||
|
foreach ($points as $point2) {
|
||||||
|
$distances[$point->name][$point2->name] = ($this->metric)($point, $point2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var float[] $distances */
|
||||||
|
return $distances;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function cluster(Point $point) : ?Point
|
||||||
|
{
|
||||||
|
if ($this->convexHulls === []) {
|
||||||
|
foreach ($this->clusters as $c => $cluster) {
|
||||||
|
$points = [];
|
||||||
|
foreach ($cluster as $p) {
|
||||||
|
$points[] = $p->coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo this is only good for 2D. Fix this for ND.
|
||||||
|
$this->convexHulls[$c] = MonotoneChain::createConvexHull($points);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->convexHulls as $c => $hull) {
|
||||||
|
if (Polygon::isPointInPolygon($point->coordinates, $hull) <= 0) {
|
||||||
|
return $hull;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the clusters of the points
|
||||||
|
*
|
||||||
|
* @param Point[] $points Points to cluster
|
||||||
|
* @param float $epsilon Max distance
|
||||||
|
* @param int $minPoints Min amount of points required for a cluster
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function generateClusters(array $points, float $epsilon, int $minPoints) : void
|
||||||
|
{
|
||||||
|
$this->noisePoints = [];
|
||||||
|
$this->clusters = [];
|
||||||
|
$this->clusteredPoints = [];
|
||||||
|
$this->points = $points;
|
||||||
|
$this->convexHulls = [];
|
||||||
|
|
||||||
|
$this->distanceMatrix = $this->generateDistanceMatrix($points);
|
||||||
|
|
||||||
|
$c = 0;
|
||||||
|
$this->clusters[$c] = [];
|
||||||
|
|
||||||
|
foreach ($this->points as $point) {
|
||||||
|
$neighbors = $this->findNeighbors($point, $epsilon);
|
||||||
|
|
||||||
|
if (\count($neighbors) < $minPoints) {
|
||||||
|
$this->noisePoints[] = $point->name;
|
||||||
|
} elseif (!\in_array($point->name, $this->clusteredPoints)) {
|
||||||
|
$this->expandCluster($point, $neighbors, $c, $epsilon, $minPoints);
|
||||||
|
++$c;
|
||||||
|
$this->clusters[$c] = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCentroids() : array
|
||||||
|
{
|
||||||
|
if (!empty($this->clusterCenters)) {
|
||||||
|
return $this->clusterCenters;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dim = \count(\reset($this->points)->getCoordinates());
|
||||||
|
foreach ($this->clusters as $cluster) {
|
||||||
|
$middle = \array_fill(0, $dim, 0);
|
||||||
|
foreach ($cluster as $point) {
|
||||||
|
for ($i = 0; $i < $dim; ++$i) {
|
||||||
|
$middle[$i] += $point->getCoordinate($i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < $dim; ++$i) {
|
||||||
|
$middle[$i] /= \count($cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clusterCenters = new Point($middle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->clusterCenters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getNoise() : array
|
||||||
|
{
|
||||||
|
return $this->noisePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getClusters() : array
|
||||||
|
{
|
||||||
|
return $this->clusters;
|
||||||
|
}
|
||||||
|
}
|
||||||
65
Algorithm/Clustering/DivisiveClustering.php
Normal file
65
Algorithm/Clustering/DivisiveClustering.php
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering points
|
||||||
|
*
|
||||||
|
* The parent category of this clustering algorithm is hierarchical clustering.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see ./AgglomerativeClustering.php
|
||||||
|
* @see ./clustering_overview.png
|
||||||
|
* @see https://en.wikipedia.org/wiki/Hierarchical_clustering
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Implement
|
||||||
|
*/
|
||||||
|
final class DivisiveClustering implements ClusteringInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCentroids() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getClusters() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function cluster(Point $point) : ?Point
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getNoise() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
278
Algorithm/Clustering/Kmeans.php
Executable file
278
Algorithm/Clustering/Kmeans.php
Executable file
|
|
@ -0,0 +1,278 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
use phpOMS\Math\Topology\MetricsND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering points
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see ./clustering_overview.png
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Kmeans implements ClusteringInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Epsilon for float comparison.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public const EPSILON = 4.88e-04;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metric to calculate the distance between two points
|
||||||
|
*
|
||||||
|
* @var \Closure
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private \Closure $metric;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Points of the cluster centers
|
||||||
|
*
|
||||||
|
* @var Point[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $clusterCenters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Points of the clusters
|
||||||
|
*
|
||||||
|
* @var Point[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $clusters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Points
|
||||||
|
*
|
||||||
|
* @var Point[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $points = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param null|\Closure $metric metric to use for the distance between two points
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(?\Closure $metric = null)
|
||||||
|
{
|
||||||
|
$this->metric = $metric ?? function (Point $a, Point $b) {
|
||||||
|
$aCoordinates = $a->coordinates;
|
||||||
|
$bCoordinates = $b->coordinates;
|
||||||
|
|
||||||
|
return MetricsND::euclidean($aCoordinates, $bCoordinates);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function cluster(Point $point) : ?Point
|
||||||
|
{
|
||||||
|
$bestCluster = null;
|
||||||
|
$bestDistance = \PHP_FLOAT_MAX;
|
||||||
|
|
||||||
|
foreach ($this->clusterCenters as $center) {
|
||||||
|
if (($distance = ($this->metric)($center, $point)) < $bestDistance) {
|
||||||
|
$bestCluster = $center;
|
||||||
|
$bestDistance = $distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bestCluster;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCentroids() : array
|
||||||
|
{
|
||||||
|
return $this->clusterCenters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getNoise() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the clusters of the points
|
||||||
|
*
|
||||||
|
* @param Point[] $points Points to cluster
|
||||||
|
* @param int<1, max> $clusters Amount of clusters
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function generateClusters(array $points, int $clusters) : void
|
||||||
|
{
|
||||||
|
$this->points = $points;
|
||||||
|
$n = \count($points);
|
||||||
|
$clusterCenters = $this->kpp($points, $clusters);
|
||||||
|
$coordinates = \count($points[0]->coordinates);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
foreach ($clusterCenters as $center) {
|
||||||
|
for ($i = 0; $i < $coordinates; ++$i) {
|
||||||
|
$center->setCoordinate($i, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($points as $point) {
|
||||||
|
$clusterPoint = $clusterCenters[$point->group];
|
||||||
|
|
||||||
|
++$clusterPoint->group;
|
||||||
|
for ($i = 0; $i < $coordinates; ++$i) {
|
||||||
|
$clusterPoint->setCoordinate($i, $clusterPoint->getCoordinate($i) + $point->getCoordinate($i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($clusterCenters as $center) {
|
||||||
|
for ($i = 0; $i < $coordinates; ++$i) {
|
||||||
|
$center->setCoordinate($i, $center->getCoordinate($i) / $center->group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$changed = 0;
|
||||||
|
foreach ($points as $point) {
|
||||||
|
$min = $this->nearestClusterCenter($point, $clusterCenters)[0];
|
||||||
|
|
||||||
|
if ($clusters !== $point->group) {
|
||||||
|
++$changed;
|
||||||
|
$point->group = $min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($changed <= $n * self::EPSILON || $n * self::EPSILON < 2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($clusterCenters as $key => $center) {
|
||||||
|
$center->group = $key;
|
||||||
|
$center->name = (string) $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->clusterCenters = $clusterCenters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the index and distance to the nearest cluster center
|
||||||
|
*
|
||||||
|
* @param Point $point Point to get the cluster for
|
||||||
|
* @param Point[] $clusterCenters All cluster centers
|
||||||
|
*
|
||||||
|
* @return array [index, distance]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function nearestClusterCenter(Point $point, array $clusterCenters) : array
|
||||||
|
{
|
||||||
|
$index = $point->group;
|
||||||
|
$dist = \PHP_FLOAT_MAX;
|
||||||
|
|
||||||
|
foreach ($clusterCenters as $key => $cPoint) {
|
||||||
|
$d = ($this->metric)($cPoint, $point);
|
||||||
|
|
||||||
|
if ($dist > $d) {
|
||||||
|
$dist = $d;
|
||||||
|
$index = $key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$index, $dist];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize cluster centers
|
||||||
|
*
|
||||||
|
* @param Point[] $points Points to use for the cluster center initialization
|
||||||
|
* @param int<0, max> $n Amount of clusters to use
|
||||||
|
*
|
||||||
|
* @return Point[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function kpp(array $points, int $n) : array
|
||||||
|
{
|
||||||
|
$clusters = [clone $points[\array_rand($points, 1)]];
|
||||||
|
|
||||||
|
$d = \array_fill(0, $n, 0.0);
|
||||||
|
|
||||||
|
for ($i = 1; $i < $n; ++$i) {
|
||||||
|
$sum = 0;
|
||||||
|
|
||||||
|
foreach ($points as $key => $point) {
|
||||||
|
$d[$key] = $this->nearestClusterCenter($point, $clusters)[1];
|
||||||
|
$sum += $d[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum *= \mt_rand(0, \mt_getrandmax()) / \mt_getrandmax();
|
||||||
|
|
||||||
|
$found = false;
|
||||||
|
foreach ($d as $key => $di) {
|
||||||
|
$sum -= $di;
|
||||||
|
|
||||||
|
// The in array check is important to avoid duplicate cluster centers
|
||||||
|
if ($sum <= 0 && !\in_array($c = $points[$key], $clusters)) {
|
||||||
|
$clusters[$i] = clone $c;
|
||||||
|
$found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!$found) {
|
||||||
|
if (!\in_array($c = $points[\array_rand($points)], $clusters)) {
|
||||||
|
$clusters[$i] = clone $c;
|
||||||
|
$found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($points as $point) {
|
||||||
|
$point->group = $this->nearestClusterCenter($point, $clusters)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getClusters() : array
|
||||||
|
{
|
||||||
|
if (!empty($this->clusters)) {
|
||||||
|
return $this->clusters;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->points as $point) {
|
||||||
|
$c = $this->cluster($point);
|
||||||
|
$this->clusters[$c?->name] = $point;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->clusters;
|
||||||
|
}
|
||||||
|
}
|
||||||
330
Algorithm/Clustering/MeanShift.php
Normal file
330
Algorithm/Clustering/MeanShift.php
Normal file
|
|
@ -0,0 +1,330 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
use phpOMS\Math\Topology\KernelsND;
|
||||||
|
use phpOMS\Math\Topology\MetricsND;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering points
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see ./clustering_overview.png
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Implement noise points
|
||||||
|
*/
|
||||||
|
final class MeanShift implements ClusteringInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Min distance for clustering
|
||||||
|
*
|
||||||
|
* As long as a point is further away as the min distance the shifting is performed
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public const MIN_DISTANCE = 0.001;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kernel function
|
||||||
|
*
|
||||||
|
* @var \Closure
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private \Closure $kernel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metric function
|
||||||
|
*
|
||||||
|
* @var \Closure
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private \Closure $metric;
|
||||||
|
|
||||||
|
private array $points;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Points outside of any cluster
|
||||||
|
*
|
||||||
|
* @var Point[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $noisePoints = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cluster points
|
||||||
|
*
|
||||||
|
* Points in clusters (helper to avoid looping the cluster array)
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $clusters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Points of the cluster centers
|
||||||
|
*
|
||||||
|
* @var Point[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $clusterCenters = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Max distance to cluster to be still considered part of cluster
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public float $groupDistanceTolerance = 0.1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* Both the metric and kernel function need to be of the same dimension.
|
||||||
|
*
|
||||||
|
* @param null|\Closure $metric Metric to use for the distance between two points
|
||||||
|
* @param null|\Closure $kernel Kernel
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(?\Closure $metric = null, ?\Closure $kernel = null)
|
||||||
|
{
|
||||||
|
$this->metric = $metric ?? function (Point $a, Point $b) {
|
||||||
|
$aCoordinates = $a->coordinates;
|
||||||
|
$bCoordinates = $b->coordinates;
|
||||||
|
|
||||||
|
return MetricsND::euclidean($aCoordinates, $bCoordinates);
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->kernel = $kernel ?? function (array $distances, array $bandwidths) {
|
||||||
|
return KernelsND::gaussianKernel($distances, $bandwidths);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the clusters of the points
|
||||||
|
*
|
||||||
|
* @param Point[] $points Points to cluster
|
||||||
|
* @param array<int|float> $bandwidth Bandwidth(s)
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function generateClusters(array $points, array $bandwidth) : void
|
||||||
|
{
|
||||||
|
$this->points = $points;
|
||||||
|
$shiftPoints = $points;
|
||||||
|
$maxMinDist = 1;
|
||||||
|
|
||||||
|
$stillShifting = \array_fill(0, \count($points), true);
|
||||||
|
|
||||||
|
$pointLength = \count($shiftPoints);
|
||||||
|
|
||||||
|
while ($maxMinDist > self::MIN_DISTANCE) {
|
||||||
|
$maxMinDist = 0;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $pointLength; ++$i) {
|
||||||
|
if (!$stillShifting[$i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pNew = $shiftPoints[$i];
|
||||||
|
$pNewStart = $pNew;
|
||||||
|
$pNew = $this->shiftPoint($pNew, $points, $bandwidth);
|
||||||
|
$dist = ($this->metric)($pNew, $pNewStart);
|
||||||
|
|
||||||
|
if ($dist > $maxMinDist) {
|
||||||
|
$maxMinDist = $dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dist < self::MIN_DISTANCE) {
|
||||||
|
$stillShifting[$i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shiftPoints[$i] = $pNew;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @todo create an array of noisePoints like in the DBSCAN. That array can be empty or not depending on the bandwidth defined
|
||||||
|
|
||||||
|
$this->clusters = $this->groupPoints($shiftPoints);
|
||||||
|
$this->clusterCenters = $shiftPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform shift on a point
|
||||||
|
*
|
||||||
|
* @param Point $point Point to shift
|
||||||
|
* @param Point $points Array of all points
|
||||||
|
* @param array<int|float> $bandwidth Bandwidth(s)
|
||||||
|
*
|
||||||
|
* @return Point
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function shiftPoint(Point $point, array $points, array $bandwidth) : Point
|
||||||
|
{
|
||||||
|
$scaleFactor = 0.0;
|
||||||
|
|
||||||
|
$shifted = clone $point;
|
||||||
|
|
||||||
|
foreach ($points as $pTemp) {
|
||||||
|
$dist = ($this->metric)($point, $pTemp);
|
||||||
|
$weight = ($this->kernel)($dist, $bandwidth);
|
||||||
|
|
||||||
|
foreach ($point->coordinates as $idx => $_) {
|
||||||
|
if (!isset($shifted->coordinates[$idx])) {
|
||||||
|
$shifted->coordinates[$idx] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$shifted->coordinates[$idx] += $pTemp->coordinates[$idx] * $weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scaleFactor += $weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($shifted->coordinates as $idx => $_) {
|
||||||
|
$shifted->coordinates[$idx] /= $scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $shifted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group points together into clusters
|
||||||
|
*
|
||||||
|
* @param Point[] $points Array of points to assign to groups
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function groupPoints(array $points) : array
|
||||||
|
{
|
||||||
|
$groupAssignment = [];
|
||||||
|
$groups = [];
|
||||||
|
$groupIndex = 0;
|
||||||
|
|
||||||
|
foreach ($points as $point) {
|
||||||
|
$nearestGroupIndex = $this->findNearestGroup($point, $groups);
|
||||||
|
|
||||||
|
if ($nearestGroupIndex === -1) {
|
||||||
|
// create new group
|
||||||
|
$groups[] = [$point];
|
||||||
|
$groupAssignment[] = $groupIndex;
|
||||||
|
|
||||||
|
++$groupIndex;
|
||||||
|
} else {
|
||||||
|
$groupAssignment[] = $nearestGroupIndex;
|
||||||
|
$groups[$nearestGroupIndex][] = $point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groupAssignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the closest cluster/group of a point
|
||||||
|
*
|
||||||
|
* @param Point $point Point to find the cluster for
|
||||||
|
* @param array<Point[]> $groups Clusters
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function findNearestGroup(Point $point, array $groups) : int
|
||||||
|
{
|
||||||
|
$nearestGroupIndex = -1;
|
||||||
|
$index = 0;
|
||||||
|
|
||||||
|
foreach ($groups as $group) {
|
||||||
|
$distanceToGroup = $this->distanceToGroup($point, $group);
|
||||||
|
|
||||||
|
if ($distanceToGroup < $this->groupDistanceTolerance) {
|
||||||
|
$nearestGroupIndex = $index;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
++$index;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $nearestGroupIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find distance of point to best cluster/group
|
||||||
|
*
|
||||||
|
* @param Point $point Point to find the cluster for
|
||||||
|
* @param Point[] $group Clusters
|
||||||
|
*
|
||||||
|
* @return float Distance
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function distanceToGroup(Point $point, array $group) : float
|
||||||
|
{
|
||||||
|
$minDistance = \PHP_FLOAT_MAX;
|
||||||
|
|
||||||
|
foreach ($group as $pt) {
|
||||||
|
$dist = ($this->metric)($point, $pt);
|
||||||
|
|
||||||
|
if ($dist < $minDistance) {
|
||||||
|
$minDistance = $dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $minDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCentroids() : array
|
||||||
|
{
|
||||||
|
return $this->clusterCenters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function cluster(Point $point) : ?Point
|
||||||
|
{
|
||||||
|
$clusterId = $this->findNearestGroup($point, $this->clusters);
|
||||||
|
|
||||||
|
return $this->clusterCenters[$clusterId] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getNoise() : array
|
||||||
|
{
|
||||||
|
return $this->noisePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getClusters() : array
|
||||||
|
{
|
||||||
|
return $this->clusters;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
Algorithm/Clustering/Point.php
Executable file
96
Algorithm/Clustering/Point.php
Executable file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Point for clustering
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Point implements PointInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Coordinates of the point
|
||||||
|
*
|
||||||
|
* @var array<int, int|float>
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public array $coordinates = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group or cluster this point belongs to
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $group = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the point
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public string $name = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param array<int, int|float> $coordinates Coordinates of the point
|
||||||
|
* @param string $name Name of the point
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(array $coordinates, string $name = '')
|
||||||
|
{
|
||||||
|
$this->coordinates = $coordinates;
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCoordinates() : array
|
||||||
|
{
|
||||||
|
return $this->coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCoordinate(int $index) : int | float
|
||||||
|
{
|
||||||
|
return $this->coordinates[$index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setCoordinate(int $index, int | float $value) : void
|
||||||
|
{
|
||||||
|
$this->coordinates[$index] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isEquals(Point $point) : bool
|
||||||
|
{
|
||||||
|
return $this->name === $point->name && $this->coordinates === $point->coordinates;
|
||||||
|
}
|
||||||
|
}
|
||||||
77
Algorithm/Clustering/PointInterface.php
Executable file
77
Algorithm/Clustering/PointInterface.php
Executable file
|
|
@ -0,0 +1,77 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Point interface.
|
||||||
|
*
|
||||||
|
* @property int $group Group
|
||||||
|
* @property string $name Name
|
||||||
|
* @property array $coordinates Coordinates
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @property array<int, int|float> $coordinates
|
||||||
|
* @property string $name
|
||||||
|
* @property int $group
|
||||||
|
*/
|
||||||
|
interface PointInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the point coordinates
|
||||||
|
*
|
||||||
|
* @return array<int, int|float>
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getCoordinates() : array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the coordinate of the point
|
||||||
|
*
|
||||||
|
* @param int $index Index of the coordinate (e.g. 0 = x);
|
||||||
|
*
|
||||||
|
* @return int|float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getCoordinate(int $index) : int | float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the coordinate of the point
|
||||||
|
*
|
||||||
|
* @param int $index Index of the coordinate (e.g. 0 = x);
|
||||||
|
* @param int|float $value Value of the coordinate
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setCoordinate(int $index, int | float $value) : void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two points are equal
|
||||||
|
*
|
||||||
|
* @param Point $point Point to compare with
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isEquals(Point $point) : bool;
|
||||||
|
}
|
||||||
61
Algorithm/Clustering/SpectralClustering.php
Normal file
61
Algorithm/Clustering/SpectralClustering.php
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Clustering;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clustering points
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Clustering
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see ./clustering_overview.png
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Implement
|
||||||
|
*/
|
||||||
|
final class SpectralClustering implements ClusteringInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCentroids() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getClusters() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function cluster(Point $point) : ?Point
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getNoise() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Algorithm/Clustering/clustering_overview.png
Normal file
BIN
Algorithm/Clustering/clustering_overview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 710 KiB |
76
Algorithm/CoinMatching/MinimumCoinProblem.php
Executable file
76
Algorithm/CoinMatching/MinimumCoinProblem.php
Executable file
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\CoinMatching
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\CoinMatching;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matching a value with a set of coins
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\CoinMatching
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class MinimumCoinProblem
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the minimum amount of coins that are required to match a value
|
||||||
|
*
|
||||||
|
* @param array $coins Types of coins available (every coin has infinite availablity)
|
||||||
|
* @param int $value Value to match with the coins
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function getMinimumCoinsForValueI(array $coins, int $value) : array
|
||||||
|
{
|
||||||
|
// amount of required coins for different values
|
||||||
|
$table = [0];
|
||||||
|
$usedCoins = [];
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $value; ++$i) {
|
||||||
|
$table[$i] = \PHP_INT_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
$m = \count($coins);
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $value; ++$i) {
|
||||||
|
for ($j = 0; $j < $m; ++$j) {
|
||||||
|
if ($coins[$j] <= $i) {
|
||||||
|
$subRes = $table[$i - $coins[$j]];
|
||||||
|
|
||||||
|
if ($subRes !== \PHP_INT_MAX
|
||||||
|
&& $subRes + 1 < $table[$i]
|
||||||
|
) {
|
||||||
|
$table[$i] = $subRes + 1;
|
||||||
|
$usedCoins[$i] = $coins[$j] === null ? ($usedCoins[$i] ?? []) : \array_merge($usedCoins[$i - $coins[$j]] ?? [], [$coins[$j]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $usedCoins[$value] ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
128
Algorithm/Frequency/Apriori.php
Normal file
128
Algorithm/Frequency/Apriori.php
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Frequency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Frequency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apriori algorithm.
|
||||||
|
*
|
||||||
|
* The algorithm checks how often a set exists in a given set of sets.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Frequency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Apriori
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate all possible subsets
|
||||||
|
*
|
||||||
|
* @param array $arr Array of elements
|
||||||
|
*
|
||||||
|
* @return array<array>
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function generateSubsets(array $arr) : array
|
||||||
|
{
|
||||||
|
$subsets = [[]];
|
||||||
|
|
||||||
|
foreach ($arr as $element) {
|
||||||
|
$newSubsets = [];
|
||||||
|
|
||||||
|
foreach ($subsets as $subset) {
|
||||||
|
$newSubsets[] = $subset;
|
||||||
|
$newSubsets[] = \array_merge($subset, [$element]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$subsets = $newSubsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($subsets[0]);
|
||||||
|
|
||||||
|
return $subsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs the apriori algorithm.
|
||||||
|
*
|
||||||
|
* The algorithm cheks how often a set exists in a given set of sets.
|
||||||
|
*
|
||||||
|
* @param array<string[]> $sets Sets of a set (e.g. [[1,2,3,4], [1,2], [1]])
|
||||||
|
* @param string[] $subset Subset to check for (empty array -> all subsets are checked)
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function apriori(array $sets, array $subset = []) : array
|
||||||
|
{
|
||||||
|
// Unique single items
|
||||||
|
$totalSet = [];
|
||||||
|
foreach ($sets as &$s) {
|
||||||
|
\sort($s);
|
||||||
|
|
||||||
|
foreach ($s as $item) {
|
||||||
|
$totalSet[] = $item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$totalSet = \array_unique($totalSet);
|
||||||
|
\sort($totalSet);
|
||||||
|
\sort($subset);
|
||||||
|
|
||||||
|
// Combinations of items
|
||||||
|
$combinations = self::generateSubsets($totalSet);
|
||||||
|
|
||||||
|
// Table
|
||||||
|
$table = [];
|
||||||
|
foreach ($combinations as &$c) {
|
||||||
|
\sort($c);
|
||||||
|
if (!empty($subset) && $c !== $subset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$table[\implode(':', $c)] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($combinations as $combination) {
|
||||||
|
if (!empty($subset) && $combination !== $subset) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($sets as $set) {
|
||||||
|
foreach ($combination as $item) {
|
||||||
|
if (!\in_array($item, $set)) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++$table[\implode(':', $combination)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $table;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
Algorithm/Graph/DependencyResolver.php
Executable file
84
Algorithm/Graph/DependencyResolver.php
Executable file
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Graph;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Graph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependency resolver class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Graph;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class DependencyResolver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Resolve dependencies
|
||||||
|
*
|
||||||
|
* @param array $graph Graph to resolve
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function resolve(array $graph) : ?array
|
||||||
|
{
|
||||||
|
$resolved = [];
|
||||||
|
$unresolved = [];
|
||||||
|
foreach ($graph as $table => $_) {
|
||||||
|
self::dependencyResolve($table, $graph, $resolved, $unresolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
return empty($unresolved) ? $resolved : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Algorithm to resolve dependencies
|
||||||
|
*
|
||||||
|
* @param int|string $item Item id
|
||||||
|
* @param array<int|string, array> $items All items
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function dependencyResolve(int | string $item, array $items, array &$resolved, array &$unresolved) : void
|
||||||
|
{
|
||||||
|
$unresolved[] = $item;
|
||||||
|
|
||||||
|
if (!isset($items[$item])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($items[$item] as $dependency) {
|
||||||
|
if (!\in_array($dependency, $unresolved)) {
|
||||||
|
$unresolved[] = $dependency;
|
||||||
|
self::dependencyResolve($dependency, $items, $resolved, $unresolved);
|
||||||
|
} else {
|
||||||
|
return; // circular dependency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!\in_array($item, $resolved)) {
|
||||||
|
$resolved[] = $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($unresolved as $key => $unres) {
|
||||||
|
if ($unres === $item) {
|
||||||
|
unset($unresolved[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
205
Algorithm/Graph/MarkovChain.php
Normal file
205
Algorithm/Graph/MarkovChain.php
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Graph
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Graph;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Markov chain
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Graph
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class MarkovChain
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Order of the markov chain
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private int $order = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trained data
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $data = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @param int $order Order of the markov chain
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(int $order = 1)
|
||||||
|
{
|
||||||
|
$this->order = $order;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create markov chain based on input
|
||||||
|
*
|
||||||
|
* @param array $values Training values
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function train(array $values) : void
|
||||||
|
{
|
||||||
|
$temp = [];
|
||||||
|
$length = \count($values) - $this->order;
|
||||||
|
|
||||||
|
$unique = \array_unique($values);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; ++$i) {
|
||||||
|
$key = [];
|
||||||
|
for ($j = 0; $j < $this->order; ++$j) {
|
||||||
|
$key[] = $values[$i + $j];
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyString = \implode(' ', $key);
|
||||||
|
|
||||||
|
if (!isset($temp[$keyString])) {
|
||||||
|
foreach ($unique as $value) {
|
||||||
|
$temp[$keyString][$value] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++$temp[$keyString][$values[$i + 1]];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($temp as $key => $values) {
|
||||||
|
$sum = \array_sum($values);
|
||||||
|
foreach ($values as $idx => $value) {
|
||||||
|
$this->data[$key][$idx] = $value / $sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set training data
|
||||||
|
*
|
||||||
|
* @param array<array<int, int>> $values Training values
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setTraining(array $values) : void
|
||||||
|
{
|
||||||
|
$this->data = $values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a markov chain based on the training data.
|
||||||
|
*
|
||||||
|
* @param int $length Length of the markov chain
|
||||||
|
* @param array $start Start values of the markov chain
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function generate(int $length, ?array $start = null) : array
|
||||||
|
{
|
||||||
|
$orderKeys = \array_keys($this->data);
|
||||||
|
$orderValues = \array_keys(\reset($this->data));
|
||||||
|
|
||||||
|
$output = $start ?? \explode(' ', $orderKeys[\array_rand($orderKeys)]);
|
||||||
|
$key = $output;
|
||||||
|
|
||||||
|
for ($i = $this->order; $i < $length; ++$i) {
|
||||||
|
$keyString = \implode(' ', $key);
|
||||||
|
|
||||||
|
$prob = \mt_rand(1, 100) / 100;
|
||||||
|
$cProb = 0.0;
|
||||||
|
$val = null;
|
||||||
|
$new = null;
|
||||||
|
|
||||||
|
foreach (($this->data[$keyString] ?? []) as $val => $p) {
|
||||||
|
$cProb += $p;
|
||||||
|
|
||||||
|
if ($prob <= $cProb) {
|
||||||
|
$new = $val;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't find possible key
|
||||||
|
$new ??= $orderValues[\array_rand($orderValues)];
|
||||||
|
|
||||||
|
$output[] = $new;
|
||||||
|
$key[] = $new;
|
||||||
|
|
||||||
|
\array_shift($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the probability for a certain markov chain.
|
||||||
|
*
|
||||||
|
* @param array $path Markov chain
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function pathProbability(array $path) : float
|
||||||
|
{
|
||||||
|
$length = \count($path);
|
||||||
|
if ($length <= $this->order) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = \array_slice($path, 0, $this->order);
|
||||||
|
|
||||||
|
$prob = 1.0;
|
||||||
|
for ($i = $this->order; $i < $length; ++$i) {
|
||||||
|
$prob *= $this->data[\implode(' ', $key)][$path[$i]] ?? 0.0;
|
||||||
|
|
||||||
|
$key[] = $path[$i];
|
||||||
|
\array_shift($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $prob;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the probability for a certain state change in a markov chain
|
||||||
|
*
|
||||||
|
* @param array $state Current state of the markov chain
|
||||||
|
* @param mixed $next Next markov state
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function stepProbability(array $state, mixed $next) : float
|
||||||
|
{
|
||||||
|
if (\count($state) !== $this->order) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->data[\implode(' ', $state)][$next] ?? 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
99
Algorithm/JobScheduling/Job.php
Executable file
99
Algorithm/JobScheduling/Job.php
Executable file
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\JobScheduling
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\JobScheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job for scheduling
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\JobScheduling
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Job implements JobInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Value of the job
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $value = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start time of the job
|
||||||
|
*
|
||||||
|
* @var \DateTime
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private \DateTime $start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* End time of the job
|
||||||
|
*
|
||||||
|
* @var \DateTime
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private ?\DateTime $end = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the job
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public string $name = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param float $value Value of the job
|
||||||
|
* @param \DateTime $start Start time of the job
|
||||||
|
* @param null|\DateTime $end End time of the job
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(float $value, \DateTime $start, ?\DateTime $end, string $name = '')
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
$this->start = $start;
|
||||||
|
$this->end = $end;
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getValue() : float
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getStart() : \DateTime
|
||||||
|
{
|
||||||
|
return $this->start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getEnd() : ?\DateTime
|
||||||
|
{
|
||||||
|
return $this->end;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Algorithm/JobScheduling/JobInterface.php
Executable file
53
Algorithm/JobScheduling/JobInterface.php
Executable file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\JobScheduling
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\JobScheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job interface.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\JobScheduling;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
interface JobInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get value of the job
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getValue() : float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get start time of the job
|
||||||
|
*
|
||||||
|
* @return \DateTime
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getStart() : \DateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get end time of the job
|
||||||
|
*
|
||||||
|
* @return \DateTime
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getEnd() : ?\DateTime;
|
||||||
|
}
|
||||||
147
Algorithm/JobScheduling/Weighted.php
Executable file
147
Algorithm/JobScheduling/Weighted.php
Executable file
|
|
@ -0,0 +1,147 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\JobScheduling
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\JobScheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job scheduling algorithm with no overlapping jobs
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\JobScheduling
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Weighted
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort jobs by end date.
|
||||||
|
*
|
||||||
|
* @param JobInterface $j1 Job 1
|
||||||
|
* @param JobInterface $j2 Job 2
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function sortByEnd(JobInterface $j1, JobInterface $j2) : int
|
||||||
|
{
|
||||||
|
if ($j1->getEnd() === null && $j2->getEnd() !== null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($j1->getEnd() === null && $j2->getEnd() === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($j1->getEnd() !== null && $j2->getEnd() === null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $j1->getEnd()->getTimestamp() <=> $j2->getEnd()->getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a none-conflicting job that comes before a defined job
|
||||||
|
*
|
||||||
|
* @param JobInterface[] $jobs List of jobs
|
||||||
|
* @param int $pivot Job to find the previous job to
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function binarySearch(array $jobs, int $pivot) : int
|
||||||
|
{
|
||||||
|
$lo = 0;
|
||||||
|
$hi = $pivot - 1;
|
||||||
|
|
||||||
|
while ($lo <= $hi) {
|
||||||
|
$mid = (int) (($lo + $hi) / 2);
|
||||||
|
|
||||||
|
if ($jobs[$mid]->getEnd() !== null
|
||||||
|
&& $jobs[$mid]->getEnd()->getTimestamp() <= $jobs[$pivot]->getStart()->getTimestamp()
|
||||||
|
) {
|
||||||
|
if ($jobs[$mid + 1]->getEnd() !== null
|
||||||
|
&& $jobs[$mid + 1]->getEnd()->getTimestamp() <= $jobs[$pivot]->getStart()->getTimestamp()
|
||||||
|
) {
|
||||||
|
$lo = $mid + 1;
|
||||||
|
} else {
|
||||||
|
return $mid;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$hi = $mid - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximize the value of the job execution without overlapping jobs
|
||||||
|
*
|
||||||
|
* @param JobInterface[] $jobs Jobs to filter
|
||||||
|
*
|
||||||
|
* @return JobInterface[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function solve(array $jobs) : array
|
||||||
|
{
|
||||||
|
$n = \count($jobs);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
\usort($jobs, function (\phpOMS\Algorithm\JobScheduling\JobInterface $j1, \phpOMS\Algorithm\JobScheduling\JobInterface $j2) : int {
|
||||||
|
return self::sortByEnd($j1, $j2);
|
||||||
|
});
|
||||||
|
|
||||||
|
$valueTable = [$jobs[0]->getValue()];
|
||||||
|
|
||||||
|
$resultTable = [];
|
||||||
|
$resultTable[0] = [$jobs[0]];
|
||||||
|
|
||||||
|
for ($i = 1; $i < $n; ++$i) {
|
||||||
|
$value = $jobs[$i]->getValue();
|
||||||
|
$jList = [$jobs[$i]];
|
||||||
|
$l = self::binarySearch($jobs, $i);
|
||||||
|
|
||||||
|
if ($l != -1) {
|
||||||
|
$value += $valueTable[$l];
|
||||||
|
$jList = \array_merge($resultTable[$l], $jList);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value > $valueTable[$i - 1]) {
|
||||||
|
$valueTable[$i] = $value;
|
||||||
|
$resultTable[$i] = $jList;
|
||||||
|
} else {
|
||||||
|
$valueTable[$i] = $valueTable[$i - 1];
|
||||||
|
$resultTable[$i] = $resultTable[$i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $resultTable[$n - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Algorithm/JobScheduling/v2/Dependency/IdleIntervalType.php
Normal file
34
Algorithm/JobScheduling/v2/Dependency/IdleIntervalType.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Machine type.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class IdleIntervalType
|
||||||
|
{
|
||||||
|
public const ACTIVE_TIME = 1; // every x hours of activity
|
||||||
|
|
||||||
|
public const JOB_TIME = 2; // every x jobs
|
||||||
|
|
||||||
|
public const FIXED_TIME = 3; // datetime
|
||||||
|
|
||||||
|
public const GENERAL_TIME = 4; // every x hours
|
||||||
|
}
|
||||||
36
Algorithm/JobScheduling/v2/Dependency/IdleTime.php
Normal file
36
Algorithm/JobScheduling/v2/Dependency/IdleTime.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Idle time.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class IdleTime
|
||||||
|
{
|
||||||
|
public int $id = 0;
|
||||||
|
|
||||||
|
public int $type = 0; // setup, shutdown, cleaning, maintenance, general, ...
|
||||||
|
|
||||||
|
public int $intervalType = IdleIntervalType::ACTIVE_TIME;
|
||||||
|
|
||||||
|
public int $interval = 0;
|
||||||
|
|
||||||
|
public int $duration = 0; // in seconds
|
||||||
|
}
|
||||||
73
Algorithm/JobScheduling/v2/Dependency/JobStep.php
Normal file
73
Algorithm/JobScheduling/v2/Dependency/JobStep.php
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job step.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class JobStep
|
||||||
|
{
|
||||||
|
public int $id = 0;
|
||||||
|
|
||||||
|
public int $order = 0;
|
||||||
|
|
||||||
|
public int $l11n = 0;
|
||||||
|
|
||||||
|
public int $machineType = 0;
|
||||||
|
|
||||||
|
public bool $machineParallelization = false;
|
||||||
|
|
||||||
|
public array $machines = [];
|
||||||
|
|
||||||
|
public int $workerType = 0;
|
||||||
|
|
||||||
|
public array $workerQualifications = []; // qualifications needed
|
||||||
|
|
||||||
|
public bool $workerParallelization = false;
|
||||||
|
|
||||||
|
public array $workers = [];
|
||||||
|
|
||||||
|
public int $material = 0;
|
||||||
|
|
||||||
|
public int $materialQuantity = 0;
|
||||||
|
|
||||||
|
public int $duration = 0; // in seconds
|
||||||
|
|
||||||
|
public int $maxHoldTime = -1; // minutes it can be halted if necessary (-1 infinite, 0 not at all)
|
||||||
|
|
||||||
|
public int $maxHoldAfterCompletion = -1; // minutes the next processing step can be postponed (-1 infinite, 0 not at all)
|
||||||
|
|
||||||
|
private int $realDuration = 0;
|
||||||
|
|
||||||
|
// depending on job completions
|
||||||
|
private array $jobDependencies = [];
|
||||||
|
|
||||||
|
public bool $shouldBeParallel = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration
|
||||||
|
* + machine type/machine specific times (e.g. setup time etc.)
|
||||||
|
* + machine-job specific times (e.g. setup time for this job which could be different from the general machine setup time)
|
||||||
|
*/
|
||||||
|
public function calculateDuration() : void
|
||||||
|
{
|
||||||
|
$this->realDuration = $this->duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Algorithm/JobScheduling/v2/Dependency/Machine.php
Normal file
32
Algorithm/JobScheduling/v2/Dependency/Machine.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Machine.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Machine
|
||||||
|
{
|
||||||
|
public int $id = 0;
|
||||||
|
|
||||||
|
public MachineType $type;
|
||||||
|
|
||||||
|
public array $idle = [];
|
||||||
|
}
|
||||||
35
Algorithm/JobScheduling/v2/Dependency/MachineType.php
Normal file
35
Algorithm/JobScheduling/v2/Dependency/MachineType.php
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Machine type.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class MachineType
|
||||||
|
{
|
||||||
|
public int $id = 0;
|
||||||
|
|
||||||
|
public array $idle = [];
|
||||||
|
|
||||||
|
public array $qualifications = []; // qualifications needed
|
||||||
|
|
||||||
|
// array of arrays, where each operator type requires certain qualifications
|
||||||
|
public array $workerTypes = [];
|
||||||
|
}
|
||||||
28
Algorithm/JobScheduling/v2/Dependency/Material.php
Normal file
28
Algorithm/JobScheduling/v2/Dependency/Material.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Material
|
||||||
|
{
|
||||||
|
public int $id = 0;
|
||||||
|
}
|
||||||
28
Algorithm/JobScheduling/v2/Dependency/Qualification.php
Normal file
28
Algorithm/JobScheduling/v2/Dependency/Qualification.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Material.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Qualification
|
||||||
|
{
|
||||||
|
public int $id = 0;
|
||||||
|
}
|
||||||
34
Algorithm/JobScheduling/v2/Dependency/Worker.php
Normal file
34
Algorithm/JobScheduling/v2/Dependency/Worker.php
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Worker
|
||||||
|
{
|
||||||
|
public int $id = 0;
|
||||||
|
|
||||||
|
public int $type = 0;
|
||||||
|
|
||||||
|
public array $idle = [];
|
||||||
|
|
||||||
|
public array $qualifications = [];
|
||||||
|
}
|
||||||
32
Algorithm/JobScheduling/v2/Dependency/WorkerType.php
Normal file
32
Algorithm/JobScheduling/v2/Dependency/WorkerType.php
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling\Dependency;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling\Dependency
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class WorkerType
|
||||||
|
{
|
||||||
|
public int $id = 0;
|
||||||
|
|
||||||
|
public array $idle = [];
|
||||||
|
|
||||||
|
public array $qualifications = [];
|
||||||
|
}
|
||||||
120
Algorithm/JobScheduling/v2/Job.php
Normal file
120
Algorithm/JobScheduling/v2/Job.php
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Job.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Job
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Id
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $id = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time of the execution
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $executionTime = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public float $priority = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value this job generates.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public float $value = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cost of executing this job.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public float $cost = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many iterations has this job been on hold in the queue.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $onhold = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many iterations has this job been in process in the queue.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $inprocessing = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What is the deadline for this job?
|
||||||
|
*
|
||||||
|
* @param \DateTime
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public \DateTime $deadline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which steps must be taken during the job execution
|
||||||
|
*
|
||||||
|
* @var JobStep[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public array $steps = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->deadline = new \DateTime('now');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the profit of the job
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getProfit() : float
|
||||||
|
{
|
||||||
|
return $this->value - $this->cost;
|
||||||
|
}
|
||||||
|
}
|
||||||
128
Algorithm/JobScheduling/v2/Notes.md
Normal file
128
Algorithm/JobScheduling/v2/Notes.md
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
## Job / Item
|
||||||
|
|
||||||
|
1. output item
|
||||||
|
2. output quantity
|
||||||
|
3. output scale factor
|
||||||
|
4. instruction manuals []
|
||||||
|
5. steps []
|
||||||
|
|
||||||
|
### Data
|
||||||
|
|
||||||
|
For single item, for this specific job (quantity plays a role) and for the current state
|
||||||
|
|
||||||
|
1. Work time planned/actual
|
||||||
|
1.1. Per worker type
|
||||||
|
1.2. Total
|
||||||
|
2. Machine time planned/actual
|
||||||
|
2.1. Per machine type
|
||||||
|
2.2. Total
|
||||||
|
3. Total duration planned/actual (is NOT work time + machine time)
|
||||||
|
4. Machines types required incl. quantity
|
||||||
|
5. Worker types required incl. quantity
|
||||||
|
6. Material costs
|
||||||
|
7. Worker costs
|
||||||
|
7.1. Per worker type
|
||||||
|
7.2. Total
|
||||||
|
8. Machine costs
|
||||||
|
8.1. Per machine type
|
||||||
|
8.2. Total
|
||||||
|
9. Progress status in %
|
||||||
|
10. Progress type (time based, step based, manual)
|
||||||
|
11. Value planned/actual
|
||||||
|
11. Costs planned/actual
|
||||||
|
12. Current step
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. Setup machine
|
||||||
|
1.1. worker types required []
|
||||||
|
1.1.1. qualifications required by worker type []
|
||||||
|
1.1.2. defined after algorithm: workers []
|
||||||
|
1.1.2.1. worker specific qualifications available []
|
||||||
|
1.2. amount of workers per type required
|
||||||
|
1.3. worker scale factor (0 = no scaling, 1 = 100% scaling)
|
||||||
|
1.4. machine types required []
|
||||||
|
1.4.1. qualifications required by machine type []
|
||||||
|
1.4.2. min capacity
|
||||||
|
1.4.3. max capacity
|
||||||
|
1.4.4. defined after algorithm: machines []
|
||||||
|
1.4.4.1. machine specific qualifications required by machine type []
|
||||||
|
1.4.4.2. machine specific min capacity
|
||||||
|
1.4.4.3. machine specific max capacity
|
||||||
|
1.5. amount of machines per type required
|
||||||
|
1.6. machine scale factor (0 = no scaling, 1 = 100% scaling)
|
||||||
|
1.7. worker / machine correlation (1 = equal scaling required, > 1 = more workers required per machine scale, < 1 = less workers required per machine scale (e.g. 1.5 -> 150% additional worker required if machines are scaled by 100%, 0.8 -> 80% additional worker required if machines are scaled by 100%))
|
||||||
|
1.8. worker duration
|
||||||
|
1.8.1. planned
|
||||||
|
1.8.1. current/actual
|
||||||
|
1.9. machine duration
|
||||||
|
1.9.1. planned
|
||||||
|
1.9.1. current/actual
|
||||||
|
1.10. total duration
|
||||||
|
1.10.1. planned
|
||||||
|
1.10.1. current/actual
|
||||||
|
1.11. duration scale factor (1 = duration equally scaled as machine/worker scaling, > 1 = longer duration with scaling, < 1 = shorter duration with scaling (e.g. 1.1 -> 110% additional duration if scaled by 100%, 0.9 -> 90 % additional duration if scaled by 100%)). The scale factor is max(worker scale, machine scale);
|
||||||
|
1.12. depends on steps []
|
||||||
|
1.13. try to parallelize? (planned/actual)
|
||||||
|
1.14. material required []
|
||||||
|
1.14.1. material id
|
||||||
|
1.14.2. planned quantity
|
||||||
|
1.14.2. actual quantity
|
||||||
|
1.15. instruction checklist []
|
||||||
|
1.16. hold time during
|
||||||
|
1.16. hold time until next stip
|
||||||
|
|
||||||
|
2. Insert material 1
|
||||||
|
3. Insert material 2
|
||||||
|
4. Mix material
|
||||||
|
5. Quality control
|
||||||
|
6. Average correction
|
||||||
|
7. Insert material 3
|
||||||
|
8. Insert material 4
|
||||||
|
9. Mix material
|
||||||
|
10. Quality control
|
||||||
|
11. Average correction
|
||||||
|
12. Fill into large bindings
|
||||||
|
13. Fill into smaller bindings
|
||||||
|
14. Quality control
|
||||||
|
15. Packaging
|
||||||
|
|
||||||
|
## Algorithm
|
||||||
|
|
||||||
|
1. Try to manufacture in one go (no large breaks in between)
|
||||||
|
2. Try to parallelize (minimize time needed for production)
|
||||||
|
3. Match deadline (if no deadline available go to "find earliest possible deadline")
|
||||||
|
3.1. Priorize close or early to deadline finish (settings dependant)
|
||||||
|
3.2. If not possilbe re-adjust pending production
|
||||||
|
3.2.1. Focus on (value, cost, ...) (settings dependant)
|
||||||
|
3.2.2. If not possible re-adjust ongoing production
|
||||||
|
3.2.2.1. Focus on (value, cost, ...) (settings dependant)
|
||||||
|
3.2.2.2. If not possible find earliest possible deadline
|
||||||
|
|
||||||
|
Constraints / To consider
|
||||||
|
|
||||||
|
1. Deadline (maybe not defined)
|
||||||
|
2. Machines
|
||||||
|
2.1. Available
|
||||||
|
2.2.1. Other jobs
|
||||||
|
2.2.2. General maintenance cleaning
|
||||||
|
2.2.3. Unforseable maintenance
|
||||||
|
2.2. Scalability by a factor
|
||||||
|
3. Worker
|
||||||
|
2.2. Available
|
||||||
|
2.2.1. Other jobs
|
||||||
|
2.2.2. General maintenance cleaning
|
||||||
|
2.2.3. Vacation/sick
|
||||||
|
2.2. Qualification
|
||||||
|
2.3. Scalability by a factor
|
||||||
|
4. Job variance (multiple corrections required)
|
||||||
|
5. Material
|
||||||
|
4.1. Available
|
||||||
|
4.2. Delivery time
|
||||||
|
6. Parallelizability
|
||||||
|
7. Stock space
|
||||||
|
8. Putting job steps on hold
|
||||||
|
9. max/min capacities
|
||||||
|
10. Scaling factors
|
||||||
46
Algorithm/JobScheduling/v2/PriorityMode.php
Normal file
46
Algorithm/JobScheduling/v2/PriorityMode.php
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling;
|
||||||
|
|
||||||
|
use phpOMS\Stdlib\Base\Enum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority type enum.
|
||||||
|
*
|
||||||
|
* Defines the different priorities in which elements from the queue can be extracted.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
abstract class PriorityMode extends Enum
|
||||||
|
{
|
||||||
|
public const FIFO = 1; // First in first out
|
||||||
|
|
||||||
|
public const LIFO = 2; // Last in first out
|
||||||
|
|
||||||
|
public const PRIORITY = 4;
|
||||||
|
|
||||||
|
public const VALUE = 8;
|
||||||
|
|
||||||
|
public const COST = 16;
|
||||||
|
|
||||||
|
public const PROFIT = 32;
|
||||||
|
|
||||||
|
public const HOLD = 64; // Longest on hold
|
||||||
|
|
||||||
|
public const EARLIEST_DEADLINE = 128; // EDF
|
||||||
|
}
|
||||||
213
Algorithm/JobScheduling/v2/ScheduleQueue.php
Normal file
213
Algorithm/JobScheduling/v2/ScheduleQueue.php
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Scheduling;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scheduler.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Scheduling
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class ScheduleQueue
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Queue
|
||||||
|
*
|
||||||
|
* @var Job[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public array $queue = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get element from queue
|
||||||
|
*
|
||||||
|
* @param int $size Amount of elements to return
|
||||||
|
* @param int $type Priority type to use for return
|
||||||
|
*
|
||||||
|
* @return Job[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function get(int $size = 1, int $type = PriorityMode::FIFO) : array
|
||||||
|
{
|
||||||
|
$jobs = [];
|
||||||
|
$keys = \array_keys($this->queue);
|
||||||
|
|
||||||
|
switch ($type) {
|
||||||
|
case PriorityMode::FIFO:
|
||||||
|
for ($i = 0; $i < $size; ++$i) {
|
||||||
|
$jobs[$i] = $this->queue[$keys[$i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PriorityMode::LIFO:
|
||||||
|
for ($i = \count($this->queue) - $size - 1; $i < $size; ++$i) {
|
||||||
|
$jobs[$i] = $this->queue[$keys[$i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PriorityMode::PRIORITY:
|
||||||
|
$queue = $this->queue;
|
||||||
|
\uasort($queue, function (Job $a, Job $b) {
|
||||||
|
return $a->priority <=> $b->priority;
|
||||||
|
});
|
||||||
|
|
||||||
|
$jobs = \array_slice($queue, 0, $size, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PriorityMode::VALUE:
|
||||||
|
$queue = $this->queue;
|
||||||
|
\uasort($queue, function (Job $a, Job $b) {
|
||||||
|
return $b->value <=> $a->value;
|
||||||
|
});
|
||||||
|
|
||||||
|
$jobs = \array_slice($queue, 0, $size, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PriorityMode::COST:
|
||||||
|
$queue = $this->queue;
|
||||||
|
\uasort($queue, function (Job $a, Job $b) {
|
||||||
|
return $a->cost <=> $b->cost;
|
||||||
|
});
|
||||||
|
|
||||||
|
$jobs = \array_slice($queue, 0, $size, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PriorityMode::PROFIT:
|
||||||
|
$queue = $this->queue;
|
||||||
|
\uasort($queue, function (Job $a, Job $b) {
|
||||||
|
return $b->getProfit() <=> $a->getProfit();
|
||||||
|
});
|
||||||
|
|
||||||
|
$jobs = \array_slice($queue, 0, $size, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PriorityMode::HOLD:
|
||||||
|
$queue = $this->queue;
|
||||||
|
\uasort($queue, function (Job $a, Job $b) {
|
||||||
|
return $b->onhold <=> $a->onhold;
|
||||||
|
});
|
||||||
|
|
||||||
|
$jobs = \array_slice($queue, 0, $size, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PriorityMode::EARLIEST_DEADLINE:
|
||||||
|
$queue = $this->queue;
|
||||||
|
\uasort($queue, function (Job $a, Job $b) {
|
||||||
|
return $a->deadline->getTimestamp() <=> $b->deadline->getTimestamp();
|
||||||
|
});
|
||||||
|
|
||||||
|
$jobs = \array_slice($queue, 0, $size, true);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert new element into queue
|
||||||
|
*
|
||||||
|
* @param int $id Element id
|
||||||
|
* @param Job $job Element to add
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function insert(int $id, Job $job) : void
|
||||||
|
{
|
||||||
|
$this->queue[$id] = $job;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop elements from the queue.
|
||||||
|
*
|
||||||
|
* This also removes the elements from the queue
|
||||||
|
*
|
||||||
|
* @param int $size Amount of elements to return
|
||||||
|
* @param int $type Priority type to use for return
|
||||||
|
*
|
||||||
|
* @return Job[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function pop(int $size = 1, int $type = PriorityMode::FIFO) : array
|
||||||
|
{
|
||||||
|
$jobs = $this->get($size, $type);
|
||||||
|
foreach ($jobs as $id => $_) {
|
||||||
|
unset($this->queue[$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $jobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases the hold counter of an element
|
||||||
|
*
|
||||||
|
* @param int $id Id of the element (0 = all elements)
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function bumpHold(int $id = 0) : void
|
||||||
|
{
|
||||||
|
if ($id === 0) {
|
||||||
|
foreach ($this->queue as $job) {
|
||||||
|
++$job->onhold;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
++$this->queue[$id]->onhold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the priority of an element
|
||||||
|
*
|
||||||
|
* @param int $id Id of the element (0 = all elements)
|
||||||
|
* @param float $priority Priority to increase by
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function adjustPriority(int $id = 0, float $priority = 0.1) : void
|
||||||
|
{
|
||||||
|
if ($id === 0) {
|
||||||
|
foreach ($this->queue as $job) {
|
||||||
|
$job->priority += $priority;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->queue[$id]->priority += $priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an element from the queue
|
||||||
|
*
|
||||||
|
* @param int $id Id of the element
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function remove(int $id) : void
|
||||||
|
{
|
||||||
|
unset($this->queue[$id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
112
Algorithm/Knapsack/Backpack.php
Executable file
112
Algorithm/Knapsack/Backpack.php
Executable file
|
|
@ -0,0 +1,112 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Knapsack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backpack for the Knapsack problem
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Backpack implements BackpackInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Maximum amount of cost this backpack can hold
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $maxCost = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current value
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $value = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current cost
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $cost = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Items inside the backpack
|
||||||
|
*
|
||||||
|
* @var array<int, array{item:ItemInterface, quantity:int|float}>
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $items = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param float $maxCost Maximum amount of costs the backpack can hold
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(float $maxCost)
|
||||||
|
{
|
||||||
|
$this->maxCost = $maxCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getValue() : float
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCost() : float
|
||||||
|
{
|
||||||
|
return $this->cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMaxCost() : float
|
||||||
|
{
|
||||||
|
return $this->maxCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getItems() : array
|
||||||
|
{
|
||||||
|
return $this->items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addItem(ItemInterface $item, int | float $quantity = 1) : void
|
||||||
|
{
|
||||||
|
$this->items[] = ['item' => $item, 'quantity' => $quantity];
|
||||||
|
$this->value += $item->getValue() * $quantity;
|
||||||
|
$this->cost += $item->getCost() * $quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
Algorithm/Knapsack/BackpackInterface.php
Executable file
74
Algorithm/Knapsack/BackpackInterface.php
Executable file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Knapsack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backpack interface.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
interface BackpackInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the value of the stored items
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getValue() : float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cost of the stored items
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getCost() : float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the max allowed costs for the items
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getMaxCost() : float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get items
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getItems() : array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add item to backpack
|
||||||
|
*
|
||||||
|
* @param ItemInterface $item Item
|
||||||
|
* @param int|float $quantity Quantity of the item
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function addItem(ItemInterface $item, int | float $quantity = 1) : void;
|
||||||
|
}
|
||||||
100
Algorithm/Knapsack/Bounded.php
Executable file
100
Algorithm/Knapsack/Bounded.php
Executable file
|
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Knapsack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bounded knapsack algorithm
|
||||||
|
*
|
||||||
|
* This algorithm only works for integer cost, values and quantities!
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Bounded
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the backpack with items
|
||||||
|
*
|
||||||
|
* This algorithm only works for integer cost, values and quantities!
|
||||||
|
*
|
||||||
|
* @param array $items Items to fill the backpack with ['item' => Item, 'quantity' => ?]
|
||||||
|
* @param BackpackInterface $backpack Backpack to fill
|
||||||
|
*
|
||||||
|
* @return BackpackInterface
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function solve(array $items, BackpackInterface $backpack) : BackpackInterface
|
||||||
|
{
|
||||||
|
$n = \count($items);
|
||||||
|
|
||||||
|
// @var int<0, max> $maxCost
|
||||||
|
$maxCost = (int) $backpack->getMaxCost();
|
||||||
|
$mm = \array_fill(0, ($maxCost + 1), 0);
|
||||||
|
$m = [];
|
||||||
|
$m[0] = $mm;
|
||||||
|
|
||||||
|
for ($i = 1; $i <= $n; ++$i) {
|
||||||
|
$m[$i] = $mm;
|
||||||
|
|
||||||
|
for ($j = 0; $j <= $maxCost; ++$j) {
|
||||||
|
$m[$i][$j] = $m[$i - 1][$j];
|
||||||
|
|
||||||
|
for ($k = 1; $k <= $items[$i - 1]['quantity']; ++$k) {
|
||||||
|
if ($k * ((int) $items[$i - 1]['item']->getCost()) > $j) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$v = $m[$i - 1][$j - $k * ((int) $items[$i - 1]['item']->getCost())] + $k * ((int) $items[$i - 1]['item']->getValue());
|
||||||
|
|
||||||
|
if ($v > $m[$i][$j]) {
|
||||||
|
$m[$i][$j] = $v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$s = 0;
|
||||||
|
for ($i = $n, $j = $maxCost; $i > 0; --$i) {
|
||||||
|
$s = 0;
|
||||||
|
$v = $m[$i][$j];
|
||||||
|
|
||||||
|
$value = (int) $items[$i - 1]['item']->getValue();
|
||||||
|
|
||||||
|
for ($k = 0; $v !== $m[$i - 1][$j] + $k * $value; ++$k) {
|
||||||
|
++$s;
|
||||||
|
$j -= (int) $items[$i - 1]['item']->getCost();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($s > 0) {
|
||||||
|
$backpack->addItem($items[$i - 1]['item'], $s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $backpack;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
Algorithm/Knapsack/Continuous.php
Executable file
84
Algorithm/Knapsack/Continuous.php
Executable file
|
|
@ -0,0 +1,84 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Knapsack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Continuous knapsack algorithm
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Continuous
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparing items
|
||||||
|
*
|
||||||
|
* @param Item[] $a Item
|
||||||
|
* @param Item[] $b Item
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function continuousComparator(array $a, array $b) : int
|
||||||
|
{
|
||||||
|
return $b['item']->getValue() / $b['item']->getCost() <=> $a['item']->getValue() / $a['item']->getCost();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the backpack with items
|
||||||
|
*
|
||||||
|
* @param array $items Items to fill the backpack with ['item' => Item, 'quantity' => ?]
|
||||||
|
* @param BackpackInterface $backpack Backpack to fill
|
||||||
|
*
|
||||||
|
* @return BackpackInterface
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function solve(array $items, BackpackInterface $backpack) : BackpackInterface
|
||||||
|
{
|
||||||
|
/* @phpstan-ignore-next-line */
|
||||||
|
\usort($items, ['self', 'continuousComparator']);
|
||||||
|
|
||||||
|
$availableSpace = $backpack->getMaxCost();
|
||||||
|
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if ($availableSpace <= 0.0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$backpack->addItem(
|
||||||
|
$item['item'],
|
||||||
|
$quantity = \min($item['quantity'], $availableSpace / $item['item']->getCost())
|
||||||
|
);
|
||||||
|
|
||||||
|
$availableSpace -= $quantity * $item['item']->getCost();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $backpack;
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Algorithm/Knapsack/Item.php
Executable file
89
Algorithm/Knapsack/Item.php
Executable file
|
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Knapsack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item in the Knapsack
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Item implements ItemInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Value of the item
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $value = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cost of the item
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $cost = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of the item
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public string $name = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param float $value Value of the item
|
||||||
|
* @param float $cost Cost of the item
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(float $value, float $cost, string $name = '')
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
$this->cost = $cost;
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getValue() : float
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getCost() : float
|
||||||
|
{
|
||||||
|
return $this->cost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getName() : string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Algorithm/Knapsack/ItemInterface.php
Executable file
53
Algorithm/Knapsack/ItemInterface.php
Executable file
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Knapsack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item interface.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Knapsack;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
interface ItemInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get value of the item
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getValue() : float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get value of the item
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getCost() : float;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the item
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getName() : string;
|
||||||
|
}
|
||||||
162
Algorithm/Maze/MazeGenerator.php
Executable file
162
Algorithm/Maze/MazeGenerator.php
Executable file
|
|
@ -0,0 +1,162 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Maze
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Maze;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maze generator
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Maze
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class MazeGenerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a random maze
|
||||||
|
*
|
||||||
|
* @param int<0, max> $width Width
|
||||||
|
* @param int<0, max> $height Height
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function random(int $width, int $height) : array
|
||||||
|
{
|
||||||
|
$n = $height * $width - 1;
|
||||||
|
$horizontal = \array_fill(0, $height, []);
|
||||||
|
$vertical = \array_fill(0, $height, []);
|
||||||
|
|
||||||
|
$pos = [\mt_rand(0, $height) - 1, \mt_rand(0, $width) - 1];
|
||||||
|
$path = [$pos];
|
||||||
|
$unvisited = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < $height + 2; ++$i) {
|
||||||
|
$unvisited[] = [];
|
||||||
|
|
||||||
|
for ($j = 0; $j < $width + 1; ++$j) {
|
||||||
|
$unvisited[$i][] = $i > 0 && $i < $height + 1 && $j > 0 && ($i !== $pos[0] + 1 || $j != $pos[1] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($n > 0) {
|
||||||
|
$potential = [
|
||||||
|
[$pos[0] + 1, $pos[1]],
|
||||||
|
[$pos[0], $pos[1] + 1],
|
||||||
|
[$pos[0] - 1, $pos[1]],
|
||||||
|
[$pos[0], $pos[1] - 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
$neighbors = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < 4; ++$i) {
|
||||||
|
if ($unvisited[$potential[$i][0] + 1][$potential[$i][1] + 1] ?? false) {
|
||||||
|
$neighbors[] = $potential[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($neighbors)) {
|
||||||
|
--$n;
|
||||||
|
|
||||||
|
$next = $neighbors[\array_rand($neighbors, 1)];
|
||||||
|
$unvisited[$next[0] + 1][$next[1] + 1] = false;
|
||||||
|
|
||||||
|
if ($next[0] === $pos[0]) {
|
||||||
|
$horizontal[$next[0]][($next[1] + $pos[1] - 1) / 2] = true;
|
||||||
|
} else {
|
||||||
|
$vertical[($next[0] + $pos[0] - 1) / 2][$next[1]] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$path[] = $next;
|
||||||
|
$pos = $next;
|
||||||
|
} else {
|
||||||
|
$pos = \array_pop($path);
|
||||||
|
|
||||||
|
if ($pos === null) {
|
||||||
|
break; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$maze = [];
|
||||||
|
for ($i = 0; $i < $height * 2 + 1; ++$i) {
|
||||||
|
$line = [];
|
||||||
|
|
||||||
|
if ($i % 2 === 0) {
|
||||||
|
for ($j = 0; $j < $width * 4 + 1; ++$j) {
|
||||||
|
if ($j % 4 === 0) {
|
||||||
|
$line[$j] = '+'; // 9
|
||||||
|
} else {
|
||||||
|
$line[$j] = $i > 0 && ($vertical[$i / 2 - 1][(int) \floor($j / 4)] ?? false) ? ' ' : '-'; // 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for ($j = 0; $j < $width * 4 + 1; ++$j) { // 2
|
||||||
|
if ($j % 4 === 0) {
|
||||||
|
$line[$j] = $j > 0 && ($horizontal[($i - 1) / 2][$j / 4 - 1] ?? false) ? ' ' : '|'; // 0 | 9
|
||||||
|
} else {
|
||||||
|
$line[$j] = ' '; // 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($i === 0) {
|
||||||
|
$line[1] = $line[2] = $line[3] = ' '; // 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($height * 2 - 1 === $i) {
|
||||||
|
$line[4 * $width] = ' '; // 2 - 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$maze[] = $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $maze;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a maze
|
||||||
|
*
|
||||||
|
* @param array<int, int[]> $maze Maze to render
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function render(array $maze) : string
|
||||||
|
{
|
||||||
|
$rendered = '';
|
||||||
|
foreach ($maze as $row) {
|
||||||
|
foreach ($row as $column) {
|
||||||
|
$rendered .= $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rendered .= "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rendered;
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Algorithm/Optimization/AntColonyOptimization.php
Normal file
27
Algorithm/Optimization/AntColonyOptimization.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Optimization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform ant colony algorithm.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class AntColonyOptimization
|
||||||
|
{
|
||||||
|
}
|
||||||
27
Algorithm/Optimization/BeesAlgorithm.php
Normal file
27
Algorithm/Optimization/BeesAlgorithm.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Optimization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform bees algorithm.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class BeesAlgorithm
|
||||||
|
{
|
||||||
|
}
|
||||||
27
Algorithm/Optimization/FireflyAlgorithm.php
Normal file
27
Algorithm/Optimization/FireflyAlgorithm.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Optimization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform firefly algorithm.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class FireflyAlgorithm
|
||||||
|
{
|
||||||
|
}
|
||||||
157
Algorithm/Optimization/GeneticOptimization.php
Normal file
157
Algorithm/Optimization/GeneticOptimization.php
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Optimization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform genetic algorithm (GA).
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class GeneticOptimization
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Fitness function (may require to pass solution space as \Closure variable)
|
||||||
|
// E.g.
|
||||||
|
// highest value of some sorts (e.g. profit)
|
||||||
|
// most elements (e.g. jobs)
|
||||||
|
// lowest costs
|
||||||
|
// combination of criteria = points (where some criteria are mandatory/optional)
|
||||||
|
public static function fitness($x)
|
||||||
|
{
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function mutate($parameters, $mutationRate)
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < \count($parameters); $i++) {
|
||||||
|
if (\mt_rand(0, 1000) / 1000 < $mutationRate) {
|
||||||
|
$parameters[$i] = 1 - $parameters[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function crossover($parent1, $parent2, $parameterCount)
|
||||||
|
{
|
||||||
|
$crossoverPoint = \mt_rand(1, $parameterCount - 1);
|
||||||
|
|
||||||
|
$child1 = \array_merge(
|
||||||
|
\array_slice($parent1, 0, $crossoverPoint),
|
||||||
|
\array_slice($parent2, $crossoverPoint)
|
||||||
|
);
|
||||||
|
|
||||||
|
$child2 = \array_merge(
|
||||||
|
\array_slice($parent2, 0, $crossoverPoint),
|
||||||
|
\array_slice($parent1, $crossoverPoint)
|
||||||
|
);
|
||||||
|
|
||||||
|
return [$child1, $child2];
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform optimization
|
||||||
|
*
|
||||||
|
* @example See unit test for example use case
|
||||||
|
*
|
||||||
|
* @param array<array> $population List of all elements with their parameters (i.e. list of "objects" as arrays).
|
||||||
|
* The constraints are defined as array values.
|
||||||
|
* @param \Closure $fitness Fitness function calculates score/feasibility of solution
|
||||||
|
* @param \Closure $mutate Mutation function to change the parameters of an "object"
|
||||||
|
* @param \Closure $crossover Crossover function to exchange parameter values between "objects".
|
||||||
|
* Sometimes single parameters can be exchanged but sometimes interdependencies exist between parameters which is why this function is required.
|
||||||
|
* @param int $generations Number of generations to create
|
||||||
|
* @param float $mutationRate Rate at which parameters are changed.
|
||||||
|
* How this is used depends on the mutate function.
|
||||||
|
*
|
||||||
|
* @return array{solutions:array, fitnesses:float[]}
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function optimize(
|
||||||
|
array $population,
|
||||||
|
\Closure $fitness,
|
||||||
|
\Closure $mutate,
|
||||||
|
\Closure $crossover,
|
||||||
|
int $generations = 500,
|
||||||
|
float $mutationRate = 0.1
|
||||||
|
) : array
|
||||||
|
{
|
||||||
|
$populationSize = \count($population);
|
||||||
|
$parameterCount = $populationSize === 0 ? 0 : \count(\reset($population));
|
||||||
|
|
||||||
|
// Genetic Algorithm Loop
|
||||||
|
for ($generation = 0; $generation < $generations; ++$generation) {
|
||||||
|
$fitnessScores = [];
|
||||||
|
foreach ($population as $parameters) {
|
||||||
|
$fitnessScores[] = ($fitness)($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select parents for crossover based on fitness scores
|
||||||
|
$parents = [];
|
||||||
|
for ($i = 0; $i < $populationSize; ++$i) {
|
||||||
|
do {
|
||||||
|
$parentIndex1 = \array_rand($population);
|
||||||
|
$parentIndex2 = \array_rand($population);
|
||||||
|
} while ($parentIndex1 === $parentIndex2);
|
||||||
|
|
||||||
|
$parents[] = $fitnessScores[$parentIndex1] > $fitnessScores[$parentIndex2]
|
||||||
|
? $population[$parentIndex1]
|
||||||
|
: $population[$parentIndex2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crossover and mutation to create next generation
|
||||||
|
$newPopulation = [];
|
||||||
|
for ($i = 0; $i < $populationSize; $i += 2) {
|
||||||
|
$crossover = ($crossover)($parents[$i], $parents[$i + 1], $parameterCount);
|
||||||
|
|
||||||
|
$child1 = ($mutate)($crossover[0], $mutationRate);
|
||||||
|
$child2 = ($mutate)($crossover[1], $mutationRate);
|
||||||
|
|
||||||
|
$newPopulation[] = $child1;
|
||||||
|
$newPopulation[] = $child2;
|
||||||
|
}
|
||||||
|
|
||||||
|
$population = $newPopulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fitnesses = [];
|
||||||
|
|
||||||
|
foreach ($population as $key => $parameters) {
|
||||||
|
$fitnesses[$key] = ($fitness)($parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
\asort($fitnesses);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'solutions' => $population,
|
||||||
|
'fitnesses' => $fitnesses,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Algorithm/Optimization/HarmonySearch.php
Normal file
27
Algorithm/Optimization/HarmonySearch.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Optimization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform harmony search algorithm.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class HarmonySearch
|
||||||
|
{
|
||||||
|
}
|
||||||
27
Algorithm/Optimization/IntelligentWaterDrops.php
Normal file
27
Algorithm/Optimization/IntelligentWaterDrops.php
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Optimization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform intelligent water drops algorithm.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class IntelligentWaterDrops
|
||||||
|
{
|
||||||
|
}
|
||||||
113
Algorithm/Optimization/SimulatedAnnealing.php
Normal file
113
Algorithm/Optimization/SimulatedAnnealing.php
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Optimization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform simulated annealing (SA).
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class SimulatedAnnealing
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
public static function costFunction($x)
|
||||||
|
{
|
||||||
|
return $x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// can be many things, e.g. swapping parameters, increasing/decreasing, random generation
|
||||||
|
public static function neighbor(array $generation, $parameterCount)
|
||||||
|
{
|
||||||
|
$newGeneration = $generation;
|
||||||
|
$randomIndex1 = \mt_rand(0, $parameterCount - 1);
|
||||||
|
$randomIndex2 = \mt_rand(0, $parameterCount - 1);
|
||||||
|
|
||||||
|
// Swap two cities in the route
|
||||||
|
$temp = $newGeneration[$randomIndex1];
|
||||||
|
$newGeneration[$randomIndex1] = $newGeneration[$randomIndex2];
|
||||||
|
$newGeneration[$randomIndex2] = $temp;
|
||||||
|
|
||||||
|
return $newGeneration;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Simulated Annealing algorithm
|
||||||
|
// @todo allow to create a solution space (currently all solutions need to be in space)
|
||||||
|
// @todo currently only replacing generations, not altering them
|
||||||
|
/**
|
||||||
|
* Perform optimization
|
||||||
|
*
|
||||||
|
* @example See unit test for example use case
|
||||||
|
*
|
||||||
|
* @param array $space List of all elements with their parameters (i.e. list of "objects" as arrays).
|
||||||
|
* The constraints are defined as array values.
|
||||||
|
* @param int $initialTemperature Starting temperature
|
||||||
|
* @param \Closure $costFunction Fitness function calculates score/feasibility of solution
|
||||||
|
* @param \Closure $neighbor Neighbor function to find a new solution/neighbor
|
||||||
|
* @param float $coolingRate Rate at which cooling takes place
|
||||||
|
* @param int $iterations Number of iterations
|
||||||
|
*
|
||||||
|
* @return array{solutions:array, costs:float[]}
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function optimize(
|
||||||
|
array $space,
|
||||||
|
int $initialTemperature,
|
||||||
|
\Closure $costFunction,
|
||||||
|
\Closure $neighbor,
|
||||||
|
float $coolingRate = 0.98,
|
||||||
|
int $iterations = 1000
|
||||||
|
) : array
|
||||||
|
{
|
||||||
|
$parameterCount = \count($space);
|
||||||
|
$currentGeneration = \reset($space);
|
||||||
|
|
||||||
|
$currentCost = ($costFunction)($currentGeneration);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $iterations; ++$i) {
|
||||||
|
$newGeneration = ($neighbor)($currentGeneration, $parameterCount);
|
||||||
|
|
||||||
|
$newCost = ($costFunction)($newGeneration);
|
||||||
|
|
||||||
|
$temperature = $initialTemperature * \pow($coolingRate, $i);
|
||||||
|
|
||||||
|
if ($newCost < $currentCost
|
||||||
|
|| \mt_rand() / \mt_getrandmax() < \exp(($currentCost - $newCost) / $temperature)
|
||||||
|
) {
|
||||||
|
$currentGeneration = $newGeneration;
|
||||||
|
$currentCost = $newCost;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'solutions' => $currentGeneration,
|
||||||
|
'costs' => $currentCost,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
118
Algorithm/Optimization/TabuSearch.php
Normal file
118
Algorithm/Optimization/TabuSearch.php
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Optimization;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform tabu search.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Optimization
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class TabuSearch
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Define your fitness function here
|
||||||
|
public static function fitness($solution) {
|
||||||
|
// Calculate and return the fitness of the solution
|
||||||
|
// This function should be tailored to your specific problem
|
||||||
|
return $solution;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define your neighborhood generation function here
|
||||||
|
public static function generateNeighbor($currentSolution) {
|
||||||
|
// Generate a neighboring solution based on the current solution
|
||||||
|
// This function should be tailored to your specific problem
|
||||||
|
return $currentSolution;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform optimization
|
||||||
|
*
|
||||||
|
* @example See unit test for example use case
|
||||||
|
*
|
||||||
|
* @param array $initialSolution List of all elements with ther parameters (i.e. list of "objects" as arrays).
|
||||||
|
* The constraints are defined as array values.
|
||||||
|
* @param \Closure $fitness Fitness function calculates score/feasability of solution
|
||||||
|
* @param \Closure $neighbor Neighbor function to find a new solution/neighbor
|
||||||
|
* @param int $tabuListSize ????
|
||||||
|
* @param int $iterations Number of iterations
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function optimize(
|
||||||
|
array $initialSolution,
|
||||||
|
\Closure $fitness,
|
||||||
|
\Closure $neighbor,
|
||||||
|
int $tabuListSize,
|
||||||
|
int $iterations
|
||||||
|
) : array
|
||||||
|
{
|
||||||
|
$currentSolution = $initialSolution;
|
||||||
|
$bestSolution = $currentSolution;
|
||||||
|
$bestFitness = \PHP_FLOAT_MIN;
|
||||||
|
$tabuList = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < $iterations; ++$i) {
|
||||||
|
$neighbors = [];
|
||||||
|
for ($j = 0; $j < $tabuListSize; ++$j) {
|
||||||
|
$neighbor = ($neighbor)($currentSolution);
|
||||||
|
$neighbors[] = $neighbor;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bestNeighbor = null;
|
||||||
|
foreach ($neighbors as $neighbor) {
|
||||||
|
if (!\in_array($neighbor, $tabuList) &&
|
||||||
|
($bestNeighbor === null
|
||||||
|
|| ($fitness)($neighbor) > ($fitness)($bestNeighbor))
|
||||||
|
) {
|
||||||
|
$bestNeighbor = $neighbor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($bestNeighbor === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tabuList[] = $bestNeighbor;
|
||||||
|
if (\count($tabuList) > $tabuListSize) {
|
||||||
|
\array_shift($tabuList);
|
||||||
|
}
|
||||||
|
|
||||||
|
$currentSolution = $bestNeighbor;
|
||||||
|
|
||||||
|
if (($score = ($fitness)($bestNeighbor)) > $bestFitness) {
|
||||||
|
$bestSolution = $bestNeighbor;
|
||||||
|
$bestFitness = $score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bestSolution;
|
||||||
|
}
|
||||||
|
}
|
||||||
116
Algorithm/PathFinding/AStar.php
Executable file
116
Algorithm/PathFinding/AStar.php
Executable file
|
|
@ -0,0 +1,116 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*
|
||||||
|
* Extended based on:
|
||||||
|
* MIT License
|
||||||
|
* (c) 2011-2012 Xueqiao Xu <xueqiaoxu@gmail.com>
|
||||||
|
* (c) PathFinding.js
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
use phpOMS\Stdlib\Base\Heap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform path finding.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class AStar implements PathFinderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function findPath(
|
||||||
|
int $startX, int $startY,
|
||||||
|
int $endX, int $endY,
|
||||||
|
Grid $grid,
|
||||||
|
int $heuristic, int $movement
|
||||||
|
) : Path
|
||||||
|
{
|
||||||
|
/** @var null|AStarNode $startNode */
|
||||||
|
$startNode = $grid->getNode($startX, $startY);
|
||||||
|
/** @var null|AStarNode $endNode */
|
||||||
|
$endNode = $grid->getNode($endX, $endY);
|
||||||
|
|
||||||
|
if ($startNode === null || $endNode === null) {
|
||||||
|
return new Path($grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
$startNode->setG(0.0);
|
||||||
|
$startNode->setF(0.0);
|
||||||
|
$startNode->setOpened(true);
|
||||||
|
|
||||||
|
$openList = new Heap(function (AStarNode $node1, AStarNode $node2) {
|
||||||
|
return $node1->getF() - $node2->getF();
|
||||||
|
});
|
||||||
|
|
||||||
|
$openList->push($startNode);
|
||||||
|
$node = null;
|
||||||
|
|
||||||
|
while (!$openList->isEmpty()) {
|
||||||
|
$node = $openList->pop();
|
||||||
|
if ($node === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var AStarNode $node */
|
||||||
|
$node->setClosed(true);
|
||||||
|
|
||||||
|
if ($node->isEqual($endNode)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var AStarNode[] $neighbors */
|
||||||
|
$neighbors = $grid->getNeighbors($node, $movement);
|
||||||
|
$neighborsLength = \count($neighbors);
|
||||||
|
for ($i = 0; $i < $neighborsLength; ++$i) {
|
||||||
|
$neighbor = $neighbors[$i];
|
||||||
|
|
||||||
|
if ($neighbor->isClosed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ng = $node->getG() + (($neighbor->getX() - $node->getX() === 0 || $neighbor->getY() - $node->getY() === 0) ? 1 : \sqrt(2));
|
||||||
|
|
||||||
|
if (!$neighbor->isOpened() || $ng < $neighbor->getG()) {
|
||||||
|
$neighbor->setG($ng);
|
||||||
|
$neighbor->setH($neighbor->getH() ?? (
|
||||||
|
$neighbor->getWeight() * Heuristic::metric($neighbor->getCoordinates(), $endNode->getCoordinates(), $heuristic)
|
||||||
|
));
|
||||||
|
$neighbor->setF($neighbor->getG() + $neighbor->getH());
|
||||||
|
$neighbor->parent = $node;
|
||||||
|
|
||||||
|
if (!$neighbor->isOpened()) {
|
||||||
|
$openList->push($neighbor);
|
||||||
|
$neighbor->setOpened(true);
|
||||||
|
} else {
|
||||||
|
$openList->update($neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = new Path($grid);
|
||||||
|
|
||||||
|
while ($node !== null) {
|
||||||
|
$path->addNode($node);
|
||||||
|
$node = $node->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
196
Algorithm/PathFinding/AStarNode.php
Executable file
196
Algorithm/PathFinding/AStarNode.php
Executable file
|
|
@ -0,0 +1,196 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node on grid.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class AStarNode extends Node
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The g score is cost of the path
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $g = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The heuristic distance is the cost to the end node
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private ?float $h = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The f score is defined as f(n) = g(n) + h(n)
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $f = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define as checked node
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private bool $isClosed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define as potential candidate
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private bool $isOpened = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is checked?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isClosed() : bool
|
||||||
|
{
|
||||||
|
return $this->isClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is potential candidate
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isOpened() : bool
|
||||||
|
{
|
||||||
|
return $this->isOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set check status
|
||||||
|
*
|
||||||
|
* @param bool $isClosed Is closed
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setClosed(bool $isClosed) : void
|
||||||
|
{
|
||||||
|
$this->isClosed = $isClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set potential candidate
|
||||||
|
*
|
||||||
|
* @param bool $isOpened Is potential candidate
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setOpened(bool $isOpened) : void
|
||||||
|
{
|
||||||
|
$this->isOpened = $isOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the g score
|
||||||
|
*
|
||||||
|
* @param float $g G score
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setG(float $g) : void
|
||||||
|
{
|
||||||
|
$this->g = $g;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the heuristic distance
|
||||||
|
*
|
||||||
|
* @param float $h H distance
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setH(?float $h) : void
|
||||||
|
{
|
||||||
|
$this->h = $h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the f score
|
||||||
|
*
|
||||||
|
* @param float $f F score
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setF(float $f) : void
|
||||||
|
{
|
||||||
|
$this->f = $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the g score
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getG() : float
|
||||||
|
{
|
||||||
|
return $this->g;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the heuristic distance
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getH() : ?float
|
||||||
|
{
|
||||||
|
return $this->h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the f score
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getF() : float
|
||||||
|
{
|
||||||
|
return $this->f;
|
||||||
|
}
|
||||||
|
}
|
||||||
225
Algorithm/PathFinding/Grid.php
Executable file
225
Algorithm/PathFinding/Grid.php
Executable file
|
|
@ -0,0 +1,225 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*
|
||||||
|
* Extended based on:
|
||||||
|
* MIT License
|
||||||
|
* (c) 2011-2012 Xueqiao Xu <xueqiaoxu@gmail.com>
|
||||||
|
* (c) PathFinding.js
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grid of nodes.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Grid
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Grid system containing all nodes
|
||||||
|
*
|
||||||
|
* @var array<int, array<int, Node>>
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $nodes = [[]];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a grid from an array
|
||||||
|
*
|
||||||
|
* [
|
||||||
|
* [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 0, 9, 9, 9, 9, 9, 0, 9, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 1, 0, 9, 0, 0, 0, 0, 0, 9, 0, 9, 9, 9,],
|
||||||
|
* [0, 0, 0, 0, 9, 0, 0, 9, 9, 9, 9, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 9, 9, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 9, 9, 9, 9, 0,],
|
||||||
|
* [0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 9, 0, 0, 0, 9, 0, 0, 9, 9, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 0, 0, 9, 9, 9, 0, 0, 9, 2, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 9, 9, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
|
||||||
|
* [0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0,],
|
||||||
|
* ]
|
||||||
|
*
|
||||||
|
* @param array<int, int[]> $gridArray Grid defined in an array (0 = empty, 1 = start, 2 = end, 9 = not walkable)
|
||||||
|
* @param string $node Node type name
|
||||||
|
*
|
||||||
|
* @return Grid
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function createGridFromArray(array $gridArray, string $node) : self
|
||||||
|
{
|
||||||
|
$grid = new self();
|
||||||
|
foreach ($gridArray as $y => $yRow) {
|
||||||
|
foreach ($yRow as $x => $xElement) {
|
||||||
|
if ($xElement === 0 || $xElement === 1 || $xElement === 2) {
|
||||||
|
/** @var \phpOMS\Algorithm\PathFinding\Node $empty */
|
||||||
|
$empty = new $node($x, $y, 1.0, true);
|
||||||
|
$grid->setNode($x, $y, $empty);
|
||||||
|
} elseif ($xElement === 9) {
|
||||||
|
/** @var \phpOMS\Algorithm\PathFinding\Node $wall */
|
||||||
|
$wall = new $node($x, $y, 1.0, false);
|
||||||
|
$grid->setNode($x, $y, $wall);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set node at position
|
||||||
|
*
|
||||||
|
* @param int $x X-Coordinate
|
||||||
|
* @param int $y Y-Coordinate
|
||||||
|
* @param Node $node Node to set
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setNode(int $x, int $y, Node $node) : void
|
||||||
|
{
|
||||||
|
$this->nodes[$y][$x] = $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get node at position
|
||||||
|
*
|
||||||
|
* @param int $x X-Coordinate
|
||||||
|
* @param int $y Y-Coordinate
|
||||||
|
*
|
||||||
|
* @return null|Node
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getNode(int $x, int $y) : ?Node
|
||||||
|
{
|
||||||
|
if (!isset($this->nodes[$y]) || !isset($this->nodes[$y][$x])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->nodes[$y][$x];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is node walkable"
|
||||||
|
*
|
||||||
|
* @param int $x X-Coordinate
|
||||||
|
* @param int $y Y-Coordinate
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isWalkable(int $x, int $y) : bool
|
||||||
|
{
|
||||||
|
return isset($this->nodes[$y]) && isset($this->nodes[$y][$x]) && $this->nodes[$y][$x]->isWalkable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get neighbors of node
|
||||||
|
*
|
||||||
|
* @param Node $node Node to get neighbors from
|
||||||
|
* @param int $movement Allowed movements
|
||||||
|
*
|
||||||
|
* @return Node[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getNeighbors(Node $node, int $movement) : array
|
||||||
|
{
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$neighbors = [];
|
||||||
|
|
||||||
|
$s0 = false;
|
||||||
|
$s1 = false;
|
||||||
|
$s2 = false;
|
||||||
|
$s3 = false;
|
||||||
|
$d0 = false;
|
||||||
|
$d1 = false;
|
||||||
|
$d2 = false;
|
||||||
|
$d3 = false;
|
||||||
|
|
||||||
|
if ($this->isWalkable($x, $y - 1)) {
|
||||||
|
$neighbors[] = $this->getNode($x, $y - 1);
|
||||||
|
$s0 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isWalkable($x + 1, $y)) {
|
||||||
|
$neighbors[] = $this->getNode($x + 1, $y);
|
||||||
|
$s1 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isWalkable($x, $y + 1)) {
|
||||||
|
$neighbors[] = $this->getNode($x, $y + 1);
|
||||||
|
$s2 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isWalkable($x - 1, $y)) {
|
||||||
|
$neighbors[] = $this->getNode($x - 1, $y);
|
||||||
|
$s3 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($movement === MovementType::STRAIGHT) {
|
||||||
|
/** @var Node[] $neighbors */
|
||||||
|
return $neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($movement === MovementType::DIAGONAL_NO_OBSTACLE) {
|
||||||
|
$d0 = $s3 && $s0;
|
||||||
|
$d1 = $s0 && $s1;
|
||||||
|
$d2 = $s1 && $s2;
|
||||||
|
$d3 = $s2 && $s3;
|
||||||
|
} elseif ($movement === MovementType::DIAGONAL_ONE_OBSTACLE) {
|
||||||
|
$d0 = $s3 || $s0;
|
||||||
|
$d1 = $s0 || $s1;
|
||||||
|
$d2 = $s1 || $s2;
|
||||||
|
$d3 = $s2 || $s3;
|
||||||
|
} elseif ($movement === MovementType::DIAGONAL) {
|
||||||
|
$d0 = true;
|
||||||
|
$d1 = true;
|
||||||
|
$d2 = true;
|
||||||
|
$d3 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($d0 && $this->isWalkable($x - 1, $y - 1)) {
|
||||||
|
$neighbors[] = $this->getNode($x - 1, $y - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($d1 && $this->isWalkable($x + 1, $y - 1)) {
|
||||||
|
$neighbors[] = $this->getNode($x + 1, $y - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($d2 && $this->isWalkable($x + 1, $y + 1)) {
|
||||||
|
$neighbors[] = $this->getNode($x + 1, $y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($d3 && $this->isWalkable($x - 1, $y + 1)) {
|
||||||
|
$neighbors[] = $this->getNode($x - 1, $y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Node[] $neighbors */
|
||||||
|
return $neighbors;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
Algorithm/PathFinding/Heuristic.php
Executable file
58
Algorithm/PathFinding/Heuristic.php
Executable file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
use phpOMS\Math\Topology\Metrics2D;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node on grid.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Heuristic
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Calculate metric/distance between two nodes.
|
||||||
|
*
|
||||||
|
* @param array<string, int|float> $node1 Array with 'x' and 'y' coordinate
|
||||||
|
* @param array<string, int|float> $node2 Array with 'x' and 'y' coordinate
|
||||||
|
* @param int $heuristic Heuristic to use for calculation
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function metric(array $node1, array $node2, int $heuristic) : float
|
||||||
|
{
|
||||||
|
if ($heuristic === HeuristicType::MANHATTAN) {
|
||||||
|
return Metrics2D::manhattan($node1, $node2);
|
||||||
|
} elseif ($heuristic === HeuristicType::EUCLIDEAN) {
|
||||||
|
return Metrics2D::euclidean($node1, $node2);
|
||||||
|
} elseif ($heuristic === HeuristicType::OCTILE) {
|
||||||
|
return Metrics2D::octile($node1, $node2);
|
||||||
|
} elseif ($heuristic === HeuristicType::MINKOWSKI) {
|
||||||
|
return Metrics2D::minkowski($node1, $node2, 1);
|
||||||
|
} elseif ($heuristic === HeuristicType::CANBERRA) {
|
||||||
|
return Metrics2D::canberra($node1, $node2);
|
||||||
|
} elseif ($heuristic === HeuristicType::BRAY_CURTIS) {
|
||||||
|
return Metrics2D::brayCurtis($node1, $node2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Metrics2D::chebyshev($node1, $node2);
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Algorithm/PathFinding/HeuristicType.php
Executable file
42
Algorithm/PathFinding/HeuristicType.php
Executable file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
use phpOMS\Stdlib\Base\Enum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heuristic type enum.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
abstract class HeuristicType extends Enum
|
||||||
|
{
|
||||||
|
public const MANHATTAN = 1;
|
||||||
|
|
||||||
|
public const EUCLIDEAN = 2;
|
||||||
|
|
||||||
|
public const OCTILE = 4;
|
||||||
|
|
||||||
|
public const CHEBYSHEV = 8;
|
||||||
|
|
||||||
|
public const MINKOWSKI = 16;
|
||||||
|
|
||||||
|
public const CANBERRA = 32;
|
||||||
|
|
||||||
|
public const BRAY_CURTIS = 64;
|
||||||
|
}
|
||||||
230
Algorithm/PathFinding/JumpPointNode.php
Executable file
230
Algorithm/PathFinding/JumpPointNode.php
Executable file
|
|
@ -0,0 +1,230 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node on grid.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class JumpPointNode extends Node
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The g score is cost of the path
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $g = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The heuristic distance is the cost to the end node
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private ?float $h = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The f score is defined as f(n) = g(n) + h(n)
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $f = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define as checked node
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private bool $isClosed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define as potential candidate
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private bool $isOpened = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The node was already tested?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private bool $isTested = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is checked?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isClosed() : bool
|
||||||
|
{
|
||||||
|
return $this->isClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is potential candidate
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isOpened() : bool
|
||||||
|
{
|
||||||
|
return $this->isOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is already tested
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isTested() : bool
|
||||||
|
{
|
||||||
|
return $this->isTested;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set check status
|
||||||
|
*
|
||||||
|
* @param bool $isClosed Is closed
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setClosed(bool $isClosed) : void
|
||||||
|
{
|
||||||
|
$this->isClosed = $isClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set potential candidate
|
||||||
|
*
|
||||||
|
* @param bool $isOpened Is potential candidate
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setOpened(bool $isOpened) : void
|
||||||
|
{
|
||||||
|
$this->isOpened = $isOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set tested
|
||||||
|
*
|
||||||
|
* @param bool $isTested Node tested?
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setTested(bool $isTested) : void
|
||||||
|
{
|
||||||
|
$this->isTested = $isTested;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the g score
|
||||||
|
*
|
||||||
|
* @param float $g G score
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setG(float $g) : void
|
||||||
|
{
|
||||||
|
$this->g = $g;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the heuristic distance
|
||||||
|
*
|
||||||
|
* @param float $h H distance
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setH(?float $h) : void
|
||||||
|
{
|
||||||
|
$this->h = $h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the f score
|
||||||
|
*
|
||||||
|
* @param float $f F score
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function setF(float $f) : void
|
||||||
|
{
|
||||||
|
$this->f = $f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the g score
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getG() : float
|
||||||
|
{
|
||||||
|
return $this->g;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the heuristic distance
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getH() : ?float
|
||||||
|
{
|
||||||
|
return $this->h;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the f score
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getF() : float
|
||||||
|
{
|
||||||
|
return $this->f;
|
||||||
|
}
|
||||||
|
}
|
||||||
731
Algorithm/PathFinding/JumpPointSearch.php
Executable file
731
Algorithm/PathFinding/JumpPointSearch.php
Executable file
|
|
@ -0,0 +1,731 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*
|
||||||
|
* Extended based on:
|
||||||
|
* MIT License
|
||||||
|
* (c) 2011-2012 Xueqiao Xu <xueqiaoxu@gmail.com>
|
||||||
|
* (c) PathFinding.js
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
use phpOMS\Stdlib\Base\Heap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform path finding.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class JumpPointSearch implements PathFinderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function findPath(
|
||||||
|
int $startX, int $startY,
|
||||||
|
int $endX, int $endY,
|
||||||
|
Grid $grid,
|
||||||
|
int $heuristic, int $movement
|
||||||
|
) : Path
|
||||||
|
{
|
||||||
|
/** @var null|JumpPointNode $startNode */
|
||||||
|
$startNode = $grid->getNode($startX, $startY);
|
||||||
|
/** @var null|JumpPointNode $endNode */
|
||||||
|
$endNode = $grid->getNode($endX, $endY);
|
||||||
|
|
||||||
|
if ($startNode === null || $endNode === null) {
|
||||||
|
return new Path($grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
$startNode->setG(0.0);
|
||||||
|
$startNode->setF(0.0);
|
||||||
|
$startNode->setOpened(true);
|
||||||
|
|
||||||
|
$openList = new Heap(function($node1, $node2) {
|
||||||
|
return $node1->getF() - $node2->getF();
|
||||||
|
});
|
||||||
|
|
||||||
|
$openList->push($startNode);
|
||||||
|
$node = null;
|
||||||
|
|
||||||
|
while (!$openList->isEmpty()) {
|
||||||
|
$node = $openList->pop();
|
||||||
|
if ($node === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JumpPointNode $node */
|
||||||
|
$node->setClosed(true);
|
||||||
|
|
||||||
|
if ($node->isEqual($endNode)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$openList = self::identifySuccessors($node, $grid, $heuristic, $movement, $endNode, $openList);
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = new Path($grid);
|
||||||
|
|
||||||
|
while ($node !== null) {
|
||||||
|
$path->addNode($node);
|
||||||
|
$node = $node->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find possible successor jump points
|
||||||
|
*
|
||||||
|
* @param JumpPointNode $node Node to find successor for
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
* @param int $heuristic Heuristic/metrics type for the distance calculation
|
||||||
|
* @param int $movement Movement type
|
||||||
|
* @param JumpPointNode $endNode End node to find path to
|
||||||
|
* @param Heap $openList Heap of open nodes
|
||||||
|
*
|
||||||
|
* @return Heap
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function identifySuccessors(JumpPointNode $node, Grid $grid, int $heuristic, int $movement, JumpPointNode $endNode, Heap $openList) : Heap
|
||||||
|
{
|
||||||
|
/** @var JumpPointNode[] $neighbors */
|
||||||
|
$neighbors = self::findNeighbors($node, $movement, $grid);
|
||||||
|
$neighborsLength = \count($neighbors);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $neighborsLength; ++$i) {
|
||||||
|
$neighbor = $neighbors[$i];
|
||||||
|
|
||||||
|
if ($neighbor === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$jumpPoint = self::jump($neighbor, $node, $endNode, $movement, $grid);
|
||||||
|
|
||||||
|
if ($jumpPoint === null || $jumpPoint->isClosed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$d = Heuristic::metric($node->getCoordinates(), $jumpPoint->getCoordinates(), HeuristicType::OCTILE);
|
||||||
|
$ng = $node->getG() + $d;
|
||||||
|
|
||||||
|
if (!$jumpPoint->isOpened() || $ng < $jumpPoint->getG()) {
|
||||||
|
$jumpPoint->setG($ng);
|
||||||
|
$jumpPoint->setH($jumpPoint->getH() ?? Heuristic::metric($jumpPoint->getCoordinates(), $endNode->getCoordinates(), $heuristic));
|
||||||
|
$jumpPoint->setF($jumpPoint->getG() + $jumpPoint->getH());
|
||||||
|
$jumpPoint->parent = $node;
|
||||||
|
|
||||||
|
if (!$jumpPoint->isOpened()) {
|
||||||
|
$openList->push($jumpPoint);
|
||||||
|
$jumpPoint->setOpened(true);
|
||||||
|
} else {
|
||||||
|
$openList->update($jumpPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $openList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find neighbor of node
|
||||||
|
*
|
||||||
|
* @param JumpPointNode $node Node to find successor for
|
||||||
|
* @param int $movement Movement type
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return Node[] Neighbors of node
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function findNeighbors(JumpPointNode $node, int $movement, Grid $grid) : array
|
||||||
|
{
|
||||||
|
if ($movement === MovementType::STRAIGHT) {
|
||||||
|
return self::findNeighborsStraight($node, $grid);
|
||||||
|
} elseif ($movement === MovementType::DIAGONAL) {
|
||||||
|
return self::findNeighborsDiagonal($node, $grid);
|
||||||
|
} elseif ($movement === MovementType::DIAGONAL_ONE_OBSTACLE) {
|
||||||
|
return self::findNeighborsDiagonalOneObstacle($node, $grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::findNeighborsDiagonalNoObstacle($node, $grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find neighbor of node
|
||||||
|
*
|
||||||
|
* @param JumpPointNode $node Node to find successor for
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return Node[] Neighbors of node
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function findNeighborsStraight(JumpPointNode $node, Grid $grid) : array
|
||||||
|
{
|
||||||
|
if ($node->parent === null) {
|
||||||
|
return $grid->getNeighbors($node, MovementType::STRAIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$px = $node->parent->getX();
|
||||||
|
$py = $node->parent->getY();
|
||||||
|
|
||||||
|
/** @var int $dx */
|
||||||
|
$dx = ($x - $px) / \max(\abs($x - $px), 1);
|
||||||
|
/** @var int $dy */
|
||||||
|
$dy = ($y - $py) / \max(\abs($y - $py), 1);
|
||||||
|
|
||||||
|
$neighbors = [];
|
||||||
|
if ($dx !== 0) {
|
||||||
|
if ($grid->isWalkable($x, $y - 1)) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x, $y + 1)) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y);
|
||||||
|
}
|
||||||
|
} elseif ($dy !== 0) {
|
||||||
|
if ($grid->isWalkable($x - 1, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x - 1, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x + 1, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + 1, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x, $y + $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + $dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JumpPointNode[] $neighbors */
|
||||||
|
return $neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find neighbor of node
|
||||||
|
*
|
||||||
|
* @param JumpPointNode $node Node to find successor for
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return Node[] Neighbors of node
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function findNeighborsDiagonal(JumpPointNode $node, Grid $grid) : array
|
||||||
|
{
|
||||||
|
if ($node->parent === null) {
|
||||||
|
return $grid->getNeighbors($node, MovementType::DIAGONAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$px = $node->parent->getX();
|
||||||
|
$py = $node->parent->getY();
|
||||||
|
|
||||||
|
/** @var int $dx */
|
||||||
|
$dx = ($x - $px) / \max(\abs($x - $px), 1);
|
||||||
|
/** @var int $dy */
|
||||||
|
$dy = ($y - $py) / \max(\abs($y - $py), 1);
|
||||||
|
|
||||||
|
$neighbors = [];
|
||||||
|
if ($dx !== 0 && $dy !== 0) {
|
||||||
|
if ($grid->isWalkable($x, $y + $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x + $dx, $y + $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x - $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x - $dx, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x, $y - $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y - $dy);
|
||||||
|
}
|
||||||
|
} elseif ($dx === 0) {
|
||||||
|
if ($grid->isWalkable($x, $y + $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x + 1, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + 1, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x - 1, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x - 1, $y + $dy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x, $y + 1)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x, $y - 1)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JumpPointNode[] $neighbors */
|
||||||
|
return $neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find neighbor of node
|
||||||
|
*
|
||||||
|
* @param JumpPointNode $node Node to find successor for
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return Node[] Neighbors of node
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function findNeighborsDiagonalOneObstacle(JumpPointNode $node, Grid $grid) : array
|
||||||
|
{
|
||||||
|
if ($node->parent === null) {
|
||||||
|
return $grid->getNeighbors($node, MovementType::DIAGONAL_ONE_OBSTACLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$px = $node->parent->getX();
|
||||||
|
$py = $node->parent->getY();
|
||||||
|
|
||||||
|
/** @var int $dx */
|
||||||
|
$dx = ($x - $px) / \max(\abs($x - $px), 1);
|
||||||
|
/** @var int $dy */
|
||||||
|
$dy = ($y - $py) / \max(\abs($y - $py), 1);
|
||||||
|
|
||||||
|
$neighbors = [];
|
||||||
|
if ($dx !== 0 && $dy !== 0) {
|
||||||
|
if ($grid->isWalkable($x, $y + $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x, $y + $dy) || $grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x - $dx, $y) && $grid->isWalkable($x, $y + $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x - $dx, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x, $y - $dy) && $grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y - $dy);
|
||||||
|
}
|
||||||
|
} elseif ($dx === 0) {
|
||||||
|
if ($grid->isWalkable($x, $y + $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + $dy);
|
||||||
|
if (!$grid->isWalkable($x + 1, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + 1, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x - 1, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x - 1, $y + $dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y);
|
||||||
|
if (!$grid->isWalkable($x, $y + 1)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$grid->isWalkable($x, $y - 1)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JumpPointNode[] $neighbors */
|
||||||
|
return $neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find neighbor of node
|
||||||
|
*
|
||||||
|
* @param JumpPointNode $node Node to find successor for
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return Node[] Neighbors of node
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function findNeighborsDiagonalNoObstacle(JumpPointNode $node, Grid $grid) : array
|
||||||
|
{
|
||||||
|
if ($node->parent === null) {
|
||||||
|
return $grid->getNeighbors($node, MovementType::DIAGONAL_NO_OBSTACLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$px = $node->parent->getX();
|
||||||
|
$py = $node->parent->getY();
|
||||||
|
|
||||||
|
/** @var int $dx */
|
||||||
|
$dx = ($x - $px) / \max(\abs($x - $px), 1);
|
||||||
|
/** @var int $dy */
|
||||||
|
$dy = ($y - $py) / \max(\abs($y - $py), 1);
|
||||||
|
|
||||||
|
$neighbors = [];
|
||||||
|
if ($dx !== 0 && $dy !== 0) {
|
||||||
|
if ($grid->isWalkable($x, $y + $dy)) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x, $y + $dy) || $grid->isWalkable($x + $dx, $y)) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y + $dy);
|
||||||
|
}
|
||||||
|
} elseif ($dx !== 0 && $dy === 0) {
|
||||||
|
$isNextWalkable = $grid->isWalkable($x + $dx, $y);
|
||||||
|
$isTopWalkable = $grid->isWalkable($x, $y + 1);
|
||||||
|
$isBottomWalkable = $grid->isWalkable($x, $y - 1);
|
||||||
|
|
||||||
|
if ($isNextWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y);
|
||||||
|
if ($isTopWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isBottomWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x + $dx, $y - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isTopWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isBottomWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y - 1);
|
||||||
|
}
|
||||||
|
} elseif ($dx === 0 && $dy !== 0) {
|
||||||
|
$isNextWalkable = $grid->isWalkable($x, $y + $dy);
|
||||||
|
$isRightWalkable = $grid->isWalkable($x + 1, $y);
|
||||||
|
$isLeftWalkable = $grid->isWalkable($x - 1, $y);
|
||||||
|
|
||||||
|
if ($isNextWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x, $y + $dy);
|
||||||
|
if ($isRightWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x + 1, $y + $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isLeftWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x - 1, $y + $dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isRightWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x + 1, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isLeftWalkable) {
|
||||||
|
$neighbors[] = $grid->getNode($x - 1, $y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var JumpPointNode[] $neighbors */
|
||||||
|
return $neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find next jump point
|
||||||
|
*
|
||||||
|
* @param null|JumpPointNode $node Node to find jump point from
|
||||||
|
* @param null|JumpPointNode $pNode Parent node
|
||||||
|
* @param JumpPointNode $endNode End node to find path to
|
||||||
|
* @param int $movement Movement type
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return null|JumpPointNode
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function jump(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, int $movement, Grid $grid) : ?JumpPointNode
|
||||||
|
{
|
||||||
|
if ($movement === MovementType::STRAIGHT) {
|
||||||
|
return self::jumpStraight($node, $pNode, $endNode, $grid);
|
||||||
|
} elseif ($movement === MovementType::DIAGONAL) {
|
||||||
|
return self::jumpDiagonal($node, $pNode, $endNode, $grid);
|
||||||
|
} elseif ($movement === MovementType::DIAGONAL_ONE_OBSTACLE) {
|
||||||
|
return self::jumpDiagonalOneObstacle($node, $pNode, $endNode, $grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::jumpDiagonalNoObstacle($node, $pNode, $endNode, $grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find next jump point
|
||||||
|
*
|
||||||
|
* @param null|JumpPointNode $node Node to find jump point from
|
||||||
|
* @param null|JumpPointNode $pNode Parent node
|
||||||
|
* @param JumpPointNode $endNode End node to find path to
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return null|JumpPointNode
|
||||||
|
*
|
||||||
|
* @throws \Exception
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function jumpStraight(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, Grid $grid) : ?JumpPointNode
|
||||||
|
{
|
||||||
|
if ($node === null || $pNode === null || !$node->isWalkable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$dx = $x - $pNode->getX();
|
||||||
|
$dy = $y - $pNode->getY();
|
||||||
|
|
||||||
|
// not always necessary but might be important for the future
|
||||||
|
$node->setTested(true);
|
||||||
|
|
||||||
|
if ($node->isEqual($endNode)) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dx !== 0) {
|
||||||
|
if (($grid->isWalkable($x, $y - 1) && !$grid->isWalkable($x - $dx, $y - 1))
|
||||||
|
|| ($grid->isWalkable($x, $y + 1) && !$grid->isWalkable($x - $dx, $y + 1))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
} elseif ($dy !== 0) {
|
||||||
|
if (($grid->isWalkable($x - 1, $y) && !$grid->isWalkable($x - 1, $y - $dy))
|
||||||
|
|| ($grid->isWalkable($x + 1, $y) && !$grid->isWalkable($x + 1, $y - $dy))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::jumpStraight($grid->getNode($x + 1, $y), $node, $endNode, $grid) !== null
|
||||||
|
|| self::jumpStraight($grid->getNode($x - 1, $y), $node, $endNode, $grid) !== null
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new \Exception('invalid movement'); // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::jumpStraight($grid->getNode($x + $dx, $y + $dy), $node, $endNode, $grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find next jump point
|
||||||
|
*
|
||||||
|
* @param null|JumpPointNode $node Node to find jump point from
|
||||||
|
* @param null|JumpPointNode $pNode Parent node
|
||||||
|
* @param JumpPointNode $endNode End node to find path to
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return null|JumpPointNode
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function jumpDiagonal(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, Grid $grid) : ?JumpPointNode
|
||||||
|
{
|
||||||
|
if ($node === null || $pNode === null || !$node->isWalkable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$dx = $x - $pNode->getX();
|
||||||
|
$dy = $y - $pNode->getY();
|
||||||
|
|
||||||
|
// not always necessary but might be important for the future
|
||||||
|
$node->setTested(true);
|
||||||
|
|
||||||
|
if ($node->isEqual($endNode)) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dx !== 0 && $dy !== 0) {
|
||||||
|
if (($grid->isWalkable($x - $dx, $y + $dy) && !$grid->isWalkable($x - $dx, $y))
|
||||||
|
|| ($grid->isWalkable($x + $dx, $y - $dy) && !$grid->isWalkable($x, $y - $dy))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::jumpDiagonal($grid->getNode($x + $dx, $y), $node, $endNode, $grid) !== null
|
||||||
|
|| self::jumpDiagonal($grid->getNode($x, $y + $dy), $node, $endNode, $grid) !== null
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
} elseif ($dx !== 0) {
|
||||||
|
if (($grid->isWalkable($x + $dx, $y + 1) && !$grid->isWalkable($x, $y + 1))
|
||||||
|
|| ($grid->isWalkable($x + $dx, $y - 1) && !$grid->isWalkable($x, $y - 1))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
} elseif (($grid->isWalkable($x + 1, $y + $dy) && !$grid->isWalkable($x + 1, $y))
|
||||||
|
|| ($grid->isWalkable($x - 1, $y + $dy) && !$grid->isWalkable($x - 1, $y))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::jumpDiagonal($grid->getNode($x + $dx, $y + $dy), $node, $endNode, $grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find next jump point
|
||||||
|
*
|
||||||
|
* @param null|JumpPointNode $node Node to find jump point from
|
||||||
|
* @param null|JumpPointNode $pNode Parent node
|
||||||
|
* @param JumpPointNode $endNode End node to find path to
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return null|JumpPointNode
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function jumpDiagonalOneObstacle(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, Grid $grid) : ?JumpPointNode
|
||||||
|
{
|
||||||
|
if ($node === null || $pNode === null || !$node->isWalkable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$dx = $x - $pNode->getX();
|
||||||
|
$dy = $y - $pNode->getY();
|
||||||
|
|
||||||
|
// not always necessary but might be important for the future
|
||||||
|
$node->setTested(true);
|
||||||
|
|
||||||
|
if ($node->isEqual($endNode)) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dx !== 0 && $dy !== 0) {
|
||||||
|
if (($grid->isWalkable($x - $dx, $y + $dy) && !$grid->isWalkable($x - $dx, $y))
|
||||||
|
|| ($grid->isWalkable($x + $dx, $y - $dy) && !$grid->isWalkable($x, $y - $dy))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::jumpDiagonalOneObstacle($grid->getNode($x + $dx, $y), $node, $endNode, $grid) !== null
|
||||||
|
|| self::jumpDiagonalOneObstacle($grid->getNode($x, $y + $dy), $node, $endNode, $grid) !== null
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
} elseif ($dx !== 0) {
|
||||||
|
if (($grid->isWalkable($x + $dx, $y + 1) && !$grid->isWalkable($x, $y + 1))
|
||||||
|
|| ($grid->isWalkable($x + $dx, $y - 1) && !$grid->isWalkable($x, $y - 1))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
} elseif (($grid->isWalkable($x + 1, $y + $dy) && !$grid->isWalkable($x + 1, $y))
|
||||||
|
|| ($grid->isWalkable($x - 1, $y + $dy) && !$grid->isWalkable($x - 1, $y))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x + $dx, $y) || $grid->isWalkable($x, $y + $dy)) {
|
||||||
|
return self::jumpDiagonalOneObstacle($grid->getNode($x + $dx, $y + $dy), $node, $endNode, $grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find next jump point
|
||||||
|
*
|
||||||
|
* @param null|JumpPointNode $node Node to find jump point from
|
||||||
|
* @param null|JumpPointNode $pNode Parent node
|
||||||
|
* @param JumpPointNode $endNode End node to find path to
|
||||||
|
* @param Grid $grid Grid of the nodes
|
||||||
|
*
|
||||||
|
* @return null|JumpPointNode
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function jumpDiagonalNoObstacle(?JumpPointNode $node, ?JumpPointNode $pNode, JumpPointNode $endNode, Grid $grid) : ?JumpPointNode
|
||||||
|
{
|
||||||
|
if ($node === null || $pNode === null || !$node->isWalkable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = $node->getX();
|
||||||
|
$y = $node->getY();
|
||||||
|
|
||||||
|
$dx = $x - $pNode->getX();
|
||||||
|
$dy = $y - $pNode->getY();
|
||||||
|
|
||||||
|
// not always necessary but might be important for the future
|
||||||
|
$node->setTested(true);
|
||||||
|
|
||||||
|
if ($node->isEqual($endNode)) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dx !== 0 && $dy !== 0) {
|
||||||
|
if (self::jumpDiagonalNoObstacle($grid->getNode($x + $dx, $y), $node, $endNode, $grid) !== null
|
||||||
|
|| self::jumpDiagonalNoObstacle($grid->getNode($x, $y + $dy), $node, $endNode, $grid) !== null
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
} elseif ($dx !== 0) {
|
||||||
|
if (($grid->isWalkable($x, $y - 1) && !$grid->isWalkable($x - $dx, $y - 1))
|
||||||
|
|| ($grid->isWalkable($x, $y + 1) && !$grid->isWalkable($x - $dx, $y + 1))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
} elseif ($dy !== 0) {
|
||||||
|
if (($grid->isWalkable($x - 1, $y) && !$grid->isWalkable($x - 1, $y - $dy))
|
||||||
|
|| ($grid->isWalkable($x + 1, $y) && !$grid->isWalkable($x + 1, $y - $dy))
|
||||||
|
) {
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($grid->isWalkable($x + $dx, $y) || $grid->isWalkable($x, $y + $dy)) {
|
||||||
|
return self::jumpDiagonalNoObstacle($grid->getNode($x + $dx, $y + $dy), $node, $endNode, $grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Algorithm/PathFinding/MovementType.php
Executable file
36
Algorithm/PathFinding/MovementType.php
Executable file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
use phpOMS\Stdlib\Base\Enum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Movement type enum.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
abstract class MovementType extends Enum
|
||||||
|
{
|
||||||
|
public const DIAGONAL = 1;
|
||||||
|
|
||||||
|
public const STRAIGHT = 2;
|
||||||
|
|
||||||
|
public const DIAGONAL_ONE_OBSTACLE = 4;
|
||||||
|
|
||||||
|
public const DIAGONAL_NO_OBSTACLE = 8;
|
||||||
|
}
|
||||||
148
Algorithm/PathFinding/Node.php
Executable file
148
Algorithm/PathFinding/Node.php
Executable file
|
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
use phpOMS\Stdlib\Base\HeapItemInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node on grid.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Node implements HeapItemInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* X-Coordinate.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private int $x = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Y-Coordinate.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private int $y = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cost of the node.
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private float $weight = 1.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be walked?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public bool $isWalkable = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent node.
|
||||||
|
*
|
||||||
|
* @var null|Node
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public ?Node $parent = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param int $x X-Coordinate
|
||||||
|
* @param int $y Y-Coordinate
|
||||||
|
* @param float $weight Cost of reaching this node
|
||||||
|
* @param bool $isWalkable Can be walked on?
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(int $x, int $y, float $weight = 1.0, bool $isWalkable = true)
|
||||||
|
{
|
||||||
|
$this->x = $x;
|
||||||
|
$this->y = $y;
|
||||||
|
$this->weight = $weight;
|
||||||
|
$this->isWalkable = $isWalkable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cost to walk on this node
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getWeight() : float
|
||||||
|
{
|
||||||
|
return $this->weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get x-coordinate
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getX() : int
|
||||||
|
{
|
||||||
|
return $this->x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get y-coordinate
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getY() : int
|
||||||
|
{
|
||||||
|
return $this->y;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is node equal to another node?
|
||||||
|
*
|
||||||
|
* @param Node $node Node to compare to
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function isEqual(HeapItemInterface $node) : bool
|
||||||
|
{
|
||||||
|
return $this->x === $node->getX() && $this->y === $node->getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the coordinates of this node.
|
||||||
|
*
|
||||||
|
* @return array<string, int> ['x' => ?, 'y' => ?]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getCoordinates() : array
|
||||||
|
{
|
||||||
|
return ['x' => $this->x, 'y' => $this->y];
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Algorithm/PathFinding/NullJumpPointNode.php
Executable file
27
Algorithm/PathFinding/NullJumpPointNode.php
Executable file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null node.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class NullJumpPointNode extends JumpPointNode
|
||||||
|
{
|
||||||
|
}
|
||||||
27
Algorithm/PathFinding/NullNode.php
Executable file
27
Algorithm/PathFinding/NullNode.php
Executable file
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Null node.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class NullNode extends Node
|
||||||
|
{
|
||||||
|
}
|
||||||
210
Algorithm/PathFinding/Path.php
Executable file
210
Algorithm/PathFinding/Path.php
Executable file
|
|
@ -0,0 +1,210 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*
|
||||||
|
* Extended based on:
|
||||||
|
* MIT License
|
||||||
|
* (c) 2011-2012 Xueqiao Xu <xueqiaoxu@gmail.com>
|
||||||
|
* (c) PathFinding.js
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path in grids.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
class Path
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Nodes in the path
|
||||||
|
*
|
||||||
|
* @var Node[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public array $nodes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grid this path belongs to
|
||||||
|
*
|
||||||
|
* @var Grid
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private Grid $grid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nodes in the path
|
||||||
|
*
|
||||||
|
* @var Node[]
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private array $expandedNodes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param Grid $grid Grid this path belongs to
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(Grid $grid)
|
||||||
|
{
|
||||||
|
$this->grid = $grid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add node to the path
|
||||||
|
*
|
||||||
|
* @param Node $node Node
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function addNode(Node $node) : void
|
||||||
|
{
|
||||||
|
$this->nodes[] = $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path length (euclidean)
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getLength() : float
|
||||||
|
{
|
||||||
|
$n = \count($this->nodes);
|
||||||
|
|
||||||
|
$dist = 0.0;
|
||||||
|
|
||||||
|
for ($i = 1; $i < $n; ++$i) {
|
||||||
|
$dx = $this->nodes[$i - 1]->getX() - $this->nodes[$i]->getX();
|
||||||
|
$dy = $this->nodes[$i - 1]->getY() - $this->nodes[$i]->getY();
|
||||||
|
|
||||||
|
$dist += \sqrt($dx * $dx + $dy * $dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the incomplete node path
|
||||||
|
*
|
||||||
|
* @return Node[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function getPath() : array
|
||||||
|
{
|
||||||
|
return $this->nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the complete node path
|
||||||
|
*
|
||||||
|
* The path may only contain the jump points or pivot points.
|
||||||
|
* In order to get every node it needs to be expanded.
|
||||||
|
*
|
||||||
|
* @return Node[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function expandPath() : array
|
||||||
|
{
|
||||||
|
if (empty($this->expandedNodes)) {
|
||||||
|
//$reverse = \array_reverse($this->nodes);
|
||||||
|
$reverse = $this->nodes;
|
||||||
|
$length = \count($reverse);
|
||||||
|
|
||||||
|
if ($length < 2) {
|
||||||
|
return $reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expanded = [];
|
||||||
|
for ($i = 0; $i < $length - 1; ++$i) {
|
||||||
|
$coord0 = $reverse[$i];
|
||||||
|
$coord1 = $reverse[$i + 1];
|
||||||
|
|
||||||
|
$interpolated = $this->interpolate($coord0, $coord1);
|
||||||
|
$expanded = \array_merge($expanded, $interpolated);
|
||||||
|
}
|
||||||
|
|
||||||
|
$expanded[] = $reverse[$length - 1];
|
||||||
|
$this->expandedNodes = $expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->expandedNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find nodes in between two nodes.
|
||||||
|
*
|
||||||
|
* The path may only contain the jump points or pivot points.
|
||||||
|
* In order to get every node it needs to be expanded.
|
||||||
|
*
|
||||||
|
* @param Node $node1 Node
|
||||||
|
* @param Node $node2 Node
|
||||||
|
*
|
||||||
|
* @return Node[]
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function interpolate(Node $node1, Node $node2) : array
|
||||||
|
{
|
||||||
|
$dx = \abs($node2->getX() - $node1->getX());
|
||||||
|
$dy = \abs($node2->getY() - $node1->getY());
|
||||||
|
|
||||||
|
$sx = ($node1->getX() < $node2->getX()) ? 1 : -1;
|
||||||
|
$sy = ($node1->getY() < $node2->getY()) ? 1 : -1;
|
||||||
|
|
||||||
|
$node = $node1;
|
||||||
|
$err = $dx - $dy;
|
||||||
|
|
||||||
|
$x0 = $node->getX();
|
||||||
|
$y0 = $node->getY();
|
||||||
|
|
||||||
|
$line = [];
|
||||||
|
while (true) {
|
||||||
|
if ($node->getX() === $node2->getX() && $node->getY() === $node2->getY()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$line[] = $node;
|
||||||
|
|
||||||
|
$e2 = 2 * $err;
|
||||||
|
|
||||||
|
if ($e2 > -$dy) {
|
||||||
|
$err -= $dy;
|
||||||
|
$x0 += $sx;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($e2 < $dx) {
|
||||||
|
$err += $dx;
|
||||||
|
$y0 += $sy;
|
||||||
|
}
|
||||||
|
|
||||||
|
$node = $this->grid->getNode($x0, $y0);
|
||||||
|
|
||||||
|
if ($node === null) {
|
||||||
|
break; // @codeCoverageIgnore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Algorithm/PathFinding/PathFinderInterface.php
Executable file
48
Algorithm/PathFinding/PathFinderInterface.php
Executable file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\PathFinding;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Path finder interface
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\PathFinding
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
interface PathFinderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Find path from one point to another
|
||||||
|
*
|
||||||
|
* @param int $startX Start point X-Coordinate
|
||||||
|
* @param int $startY Start point Y-Coordinate
|
||||||
|
* @param int $endX End point X-Coordinate
|
||||||
|
* @param int $endY End point Y-Coordinate
|
||||||
|
* @param Grid $grid Grid with the walkable points
|
||||||
|
* @param int $heuristic Heuristic algorithm to use in order to calculate the distance for a good path
|
||||||
|
* @param int $movement Allowed movement (e.g. straight, diagonal, ...)
|
||||||
|
*
|
||||||
|
* @return Path
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function findPath(
|
||||||
|
int $startX, int $startY,
|
||||||
|
int $endX, int $endY,
|
||||||
|
Grid $grid,
|
||||||
|
int $heuristic, int $movement
|
||||||
|
) : Path;
|
||||||
|
}
|
||||||
85
Algorithm/Rating/BradleyTerry.php
Normal file
85
Algorithm/Rating/BradleyTerry.php
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Rating;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate rating strength using the Bradley Terry model
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see https://en.wikipedia.org/wiki/Bradley%E2%80%93Terry_model
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class BradleyTerry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Rate the strongest to the weakest team based on historic performances (wins/losses)
|
||||||
|
*
|
||||||
|
* The following example contains match results (matrix) of teams A-D facing each other (each point is a victory).
|
||||||
|
* @example rating(
|
||||||
|
* [
|
||||||
|
* 'A' => ['A' => 0, 'B' => 2, 'C' => 0, 'D' => 1],
|
||||||
|
* 'B' => ['A' => 3, 'B' => 0, 'C' => 5, 'D' => 0],
|
||||||
|
* 'C' => ['A' => 0, 'B' => 3, 'C' => 0, 'D' => 1],
|
||||||
|
* 'D' => ['A' => 4, 'B' => 0, 'C' => 3, 'D' => 0],
|
||||||
|
* ],
|
||||||
|
* 10
|
||||||
|
* ) // [0.640, 1.043, 0.660, 2.270] -> D is strongest
|
||||||
|
*
|
||||||
|
* @param array[] $history Historic results
|
||||||
|
* @param int $iterations Iterations for estimation
|
||||||
|
*
|
||||||
|
* @return float[] Array of "strength" scores (highest = strongest)
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function rating(array $history, int $iterations = 20) : array
|
||||||
|
{
|
||||||
|
$keys = \array_keys($history);
|
||||||
|
$pOld = [];
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$pOld[$key] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$p = $pOld;
|
||||||
|
for ($i = 0; $i < $iterations; ++$i) {
|
||||||
|
foreach ($history as $idx => $row) {
|
||||||
|
$W = \array_sum($row);
|
||||||
|
|
||||||
|
$d = 0;
|
||||||
|
foreach ($history as $idx2 => $_) {
|
||||||
|
if ($idx === $idx2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$d += ($history[$idx][$idx2] + $history[$idx2][$idx])
|
||||||
|
/ ($pOld[$idx] + $pOld[$idx2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$p[$idx] = $W / $d;
|
||||||
|
}
|
||||||
|
|
||||||
|
$norm = \array_sum($p);
|
||||||
|
foreach ($p as $idx => $_) {
|
||||||
|
$p[$idx] /= $norm;
|
||||||
|
}
|
||||||
|
|
||||||
|
$pOld = $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $p;
|
||||||
|
}
|
||||||
|
}
|
||||||
110
Algorithm/Rating/Elo.php
Normal file
110
Algorithm/Rating/Elo.php
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Rating;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elo rating calculation using Elo rating
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see https://en.wikipedia.org/wiki/Elo_rating_system
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Elo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* ELO change rate
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $K = 32;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default elo to use for new players
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $DEFAULT_ELO = 1500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lowest elo allowed
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $MIN_ELO = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the elo rating
|
||||||
|
*
|
||||||
|
* @param int $elo Current player elo
|
||||||
|
* @param int[] $oElo Current elo of all opponents
|
||||||
|
* @param float[] $s Match results against the opponents (1 = victor, 0 = loss, 0.5 = draw)
|
||||||
|
*
|
||||||
|
* @return array{elo:int}
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function rating(int $elo, array $oElo, array $s) : array
|
||||||
|
{
|
||||||
|
$eloNew = $elo;
|
||||||
|
foreach ($oElo as $idx => $o) {
|
||||||
|
$expected = 1 / (1 + 10 ** (($o - $elo) / 400));
|
||||||
|
$r = $this->K * ($s[$idx] - $expected);
|
||||||
|
|
||||||
|
$eloNew += (int) \round($r);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'elo' => (int) \max($eloNew, $this->MIN_ELO),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate an approximated win probability based on elo points.
|
||||||
|
*
|
||||||
|
* @param int $elo1 Elo of the player we want to calculate the win probability for
|
||||||
|
* @param int $elo2 Opponent elo
|
||||||
|
* @param bool $canDraw Is a draw possible?
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function winProbability(int $elo1, int $elo2, bool $canDraw = false) : float
|
||||||
|
{
|
||||||
|
return $canDraw
|
||||||
|
? -1.0 // @todo implement
|
||||||
|
: 1 / (1 + \pow(10, ($elo2 - $elo1) / 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate an approximated draw probability based on elo points.
|
||||||
|
*
|
||||||
|
* @param int $elo1 Elo of the player we want to calculate the win probability for
|
||||||
|
* @param int $elo2 Opponent elo
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function drawProbability(int $elo1, int $elo2) : float
|
||||||
|
{
|
||||||
|
return -1.0; // @todo implement
|
||||||
|
}
|
||||||
|
}
|
||||||
171
Algorithm/Rating/Glicko1.php
Normal file
171
Algorithm/Rating/Glicko1.php
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Rating;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elo rating calculation using Glicko-1
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see https://en.wikipedia.org/wiki/Glicko_rating_system
|
||||||
|
* @see http://www.glicko.net/glicko/glicko.pdf
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Glicko1
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Helper constant
|
||||||
|
*
|
||||||
|
* @latex Q = ln(10) / 400
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private const Q = 0.00575646273;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default elo to use for new players
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $DEFAULT_ELO = 1500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default rd to use for new players
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $DEFAULT_RD = 350;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* C (constant) for RD caclulation
|
||||||
|
*
|
||||||
|
* This is used to adjust the RD value based on the time from the last time a player played a match
|
||||||
|
*
|
||||||
|
* @latex RD = min\left(\sqrt{RD_0^2 + c^2t}, 350\right)
|
||||||
|
*
|
||||||
|
* @see calculateC();
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public float $DEFAULT_C = 34.6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lowest elo allowed
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $MIN_ELO = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lowest rd allowed
|
||||||
|
*
|
||||||
|
* @example 50 means that the player rating is probably between -100 / +100 of the current rating
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $MIN_RD = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the C value.
|
||||||
|
*
|
||||||
|
* This is only necessary if you change the DEFAULT_RD, want a different rating period or have significantly different average RD values.
|
||||||
|
*
|
||||||
|
* @param int $ratingPeriods Time without matches until the RD returns to the default RD
|
||||||
|
* @param int $avgRD Average RD
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function calculateC(int $ratingPeriods = 100, int $avgRD = 50) : void
|
||||||
|
{
|
||||||
|
$this->DEFAULT_C = \sqrt(($this->DEFAULT_RD ** 2 - $avgRD ** 2) / $ratingPeriods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the glicko-1 elo
|
||||||
|
*
|
||||||
|
* @param int $elo Current player "elo"
|
||||||
|
* @param int $rdOld Current player deviation (RD)
|
||||||
|
* @param int $lastMatchDate Last match date used to calculate the time difference (can be days, months, ... depending on your match interval)
|
||||||
|
* @param int $matchDate Match date (usually day)
|
||||||
|
* @param int[] $oElo Opponent "elo"
|
||||||
|
* @param float[] $s Match results (1 = victor, 0 = loss, 0.5 = draw)
|
||||||
|
* @param int[] $oRd Opponent deviation (RD)
|
||||||
|
*
|
||||||
|
* @return array{elo:int, rd:int}
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function rating(
|
||||||
|
int $elo = 1500,
|
||||||
|
int $rdOld = 50,
|
||||||
|
int $lastMatchDate = 0,
|
||||||
|
int $matchDate = 0,
|
||||||
|
array $oElo = [],
|
||||||
|
array $s = [],
|
||||||
|
array $oRd = []
|
||||||
|
) : array
|
||||||
|
{
|
||||||
|
// Step 1:
|
||||||
|
$E = [];
|
||||||
|
$gRD = [];
|
||||||
|
|
||||||
|
$RD = \min(
|
||||||
|
350,
|
||||||
|
\max(
|
||||||
|
\sqrt(
|
||||||
|
$rdOld * $rdOld
|
||||||
|
+ $this->DEFAULT_C * $this->DEFAULT_C * \max(0, $matchDate - $lastMatchDate)
|
||||||
|
),
|
||||||
|
$this->MIN_RD
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 2:
|
||||||
|
foreach ($oElo as $id => $e) {
|
||||||
|
$gRD_t = 1 / (\sqrt(1 + 3 * self::Q * self::Q * $oRd[$id] * $oRd[$id] / (\M_PI * \M_PI)));
|
||||||
|
$gRD[$id] = $gRD_t;
|
||||||
|
$E[$id] = 1 / (1 + \pow(10, $gRD_t * ($elo - $e) / -400));
|
||||||
|
}
|
||||||
|
|
||||||
|
$d = 0;
|
||||||
|
foreach ($E as $id => $_) {
|
||||||
|
$d += $gRD[$id] * $gRD[$id] * $E[$id] * (1 - $E[$id]);
|
||||||
|
}
|
||||||
|
$d2 = 1 / (self::Q * self::Q * $d);
|
||||||
|
|
||||||
|
$r = 0;
|
||||||
|
foreach ($E as $id => $_) {
|
||||||
|
$r += $gRD[$id] * ($s[$id] - $E[$id]);
|
||||||
|
}
|
||||||
|
$r = $elo + self::Q / (1 / ($RD * $RD) + 1 / $d2) * $r;
|
||||||
|
|
||||||
|
// Step 3:
|
||||||
|
$RD_ = \sqrt(1 / (1 / ($RD * $RD) + 1 / $d2));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'elo' => (int) \max((int) $r, $this->MIN_ELO),
|
||||||
|
'rd' => (int) \max($RD_, $this->MIN_RD),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
179
Algorithm/Rating/Glicko2.php
Normal file
179
Algorithm/Rating/Glicko2.php
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Rating;
|
||||||
|
|
||||||
|
use phpOMS\Math\Solver\Root\Bisection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elo rating calculation using Glicko-2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see https://en.wikipedia.org/wiki/Glicko_rating_system
|
||||||
|
* @see http://www.glicko.net/glicko/glicko2.pdf
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class Glicko2
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Glicko scale factor
|
||||||
|
*
|
||||||
|
* @latex Q = 400 / ln(10)
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private const Q = 173.7177927613;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constraint for the volatility over time (smaller = stronger constraint)
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public float $tau = 0.5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default elo to use for new players
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $DEFAULT_ELO = 1500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default rd to use for new players
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $DEFAULT_RD = 350;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valatility (sigma)
|
||||||
|
*
|
||||||
|
* Expected flactuation = how erratic is the player's performance
|
||||||
|
*
|
||||||
|
* @var float
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public float $DEFAULT_VOLATILITY = 0.06;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lowest elo allowed
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $MIN_ELO = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lowest rd allowed
|
||||||
|
*
|
||||||
|
* @example 50 means that the player rating is probably between -100 / +100 of the current rating
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public int $MIN_RD = 50;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the glicko-2 elo
|
||||||
|
*
|
||||||
|
* @example $glicko->elo(1500, 200, 0.06, [1,0,0], [1400,1550,1700], [30,100,300]) // 1464, 151, 0.059
|
||||||
|
*
|
||||||
|
* @param int $elo Current player "elo"
|
||||||
|
* @param int $rdOld Current player deviation (RD)
|
||||||
|
* @param float $volOld Last match date used to calculate the time difference (can be days, months, ... depending on your match interval)
|
||||||
|
* @param int[] $oElo Opponent "elo"
|
||||||
|
* @param float[] $s Match results (1 = victor, 0 = loss, 0.5 = draw)
|
||||||
|
* @param int[] $oRd Opponent deviation (RD)
|
||||||
|
*
|
||||||
|
* @return array{elo:int, rd:int, vol:float}
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function rating(
|
||||||
|
int $elo = 1500,
|
||||||
|
int $rdOld = 50,
|
||||||
|
float $volOld = 0.06,
|
||||||
|
array $oElo = [],
|
||||||
|
array $s = [],
|
||||||
|
array $oRd = []
|
||||||
|
) : array
|
||||||
|
{
|
||||||
|
$tau = $this->tau;
|
||||||
|
|
||||||
|
// Step 0:
|
||||||
|
$rdOld /= self::Q;
|
||||||
|
$elo = ($elo - $this->DEFAULT_ELO) / self::Q;
|
||||||
|
|
||||||
|
foreach ($oElo as $idx => $value) {
|
||||||
|
$oElo[$idx] = ($value - $this->DEFAULT_ELO) / self::Q;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($oRd as $idx => $value) {
|
||||||
|
$oRd[$idx] = $value / self::Q;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1:
|
||||||
|
$g = [];
|
||||||
|
foreach ($oRd as $idx => $rd) {
|
||||||
|
$g[$idx] = 1 / \sqrt(1 + 3 * $rd * $rd / (\M_PI * \M_PI));
|
||||||
|
}
|
||||||
|
|
||||||
|
$E = [];
|
||||||
|
foreach ($oElo as $idx => $oe) {
|
||||||
|
$E[$idx] = 1 / (1 + \exp(-$g[$idx] * ($elo - $oe)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$v = 0;
|
||||||
|
foreach ($g as $idx => $t) {
|
||||||
|
$v += $t * $t * $E[$idx] * (1 - $E[$idx]);
|
||||||
|
}
|
||||||
|
$v = 1 / $v;
|
||||||
|
|
||||||
|
$tDelta = 0;
|
||||||
|
foreach ($g as $idx => $t) {
|
||||||
|
$tDelta += $t * ($s[$idx] - $E[$idx]);
|
||||||
|
}
|
||||||
|
$Delta = $v * $tDelta;
|
||||||
|
|
||||||
|
// Step 2:
|
||||||
|
$fn = function($x) use ($Delta, $rdOld, $v, $tau, $volOld)
|
||||||
|
{
|
||||||
|
return 0.5 * (\exp($x) * ($Delta ** 2 - $rdOld ** 2 - $v - \exp($x))) / (($rdOld ** 2 + $v + \exp($x)) ** 2)
|
||||||
|
- ($x - \log($volOld ** 2)) / ($tau ** 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
$root = Bisection::root($fn, -100, 100, 1000);
|
||||||
|
$vol = \exp($root / 2);
|
||||||
|
|
||||||
|
// Step 3:
|
||||||
|
$RD = 1 / \sqrt(1 / ($rdOld ** 2 + $vol ** 2) + 1 / $v);
|
||||||
|
$r = $elo + $RD ** 2 * $tDelta;
|
||||||
|
|
||||||
|
// Undo step 0:
|
||||||
|
$RD = self::Q * $RD;
|
||||||
|
$r = self::Q * $r + $this->DEFAULT_ELO;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'elo' => (int) \max($r, $this->MIN_ELO),
|
||||||
|
'rd' => (int) \max($RD, $this->MIN_RD),
|
||||||
|
'vol' => $vol,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
290
Algorithm/Rating/TrueSkill.php
Normal file
290
Algorithm/Rating/TrueSkill.php
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @copyright Microsoft
|
||||||
|
* @license This algorithm may be patented by Microsoft, verify and acquire a license if necessary
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Rating;
|
||||||
|
|
||||||
|
use phpOMS\Math\Stochastic\Distribution\NormalDistribution;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elo rating calculation using Elo rating
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see https://www.moserware.com/assets/computing-your-skill/The%20Math%20Behind%20TrueSkill.pdf
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @todo Implement https://github.com/sublee/trueskill/blob/master/trueskill/__init__.py
|
||||||
|
* https://github.com/Karaka-Management/phpOMS/issues/337
|
||||||
|
*/
|
||||||
|
// phpcs:ignoreFile
|
||||||
|
class TrueSkill
|
||||||
|
{
|
||||||
|
public const DEFAULT_MU = 25;
|
||||||
|
|
||||||
|
public const DEFAULT_SIGMA = 25 / 3;
|
||||||
|
|
||||||
|
public const DEFAULT_BETA = 25 / 3 / 2;
|
||||||
|
|
||||||
|
public const DEFAULT_TAU = 25 / 3 / 100;
|
||||||
|
|
||||||
|
public const DEFAULT_DRAW_PROBABILITY = 0.1;
|
||||||
|
|
||||||
|
private float $mu = 0.0;
|
||||||
|
|
||||||
|
private float $sigma = 0.0;
|
||||||
|
|
||||||
|
private float $beta = 0.0;
|
||||||
|
|
||||||
|
private float $tau = 0.0;
|
||||||
|
|
||||||
|
private float $drawProbability = 0.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param null|float $mu Mu
|
||||||
|
* @param null|float $sigma Sigma
|
||||||
|
* @param null|float $beta Beta
|
||||||
|
* @param null|float $tau Tau
|
||||||
|
* @param null|float $drawProbability Draw probability
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
?float $mu = null,
|
||||||
|
?float $sigma = null,
|
||||||
|
?float $beta = null,
|
||||||
|
?float $tau = null,
|
||||||
|
?float $drawProbability = null)
|
||||||
|
{
|
||||||
|
$this->mu = $mu ?? self::DEFAULT_MU;
|
||||||
|
$this->sigma = $sigma ?? self::DEFAULT_SIGMA;
|
||||||
|
$this->beta = $beta ?? self::DEFAULT_BETA;
|
||||||
|
$this->tau = $tau ?? self::DEFAULT_TAU;
|
||||||
|
$this->drawProbability = $drawProbability ?? self::DEFAULT_DRAW_PROBABILITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate win probability
|
||||||
|
*
|
||||||
|
* @param array $team1 Team 1
|
||||||
|
* @param array $team2 Team 2
|
||||||
|
* @param float $drawMargin Draw margin
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public function winProbability(array $team1, array $team2, float $drawMargin = 0.0) : float
|
||||||
|
{
|
||||||
|
$sigmaSum = 0.0;
|
||||||
|
$mu1 = 0.0;
|
||||||
|
foreach ($team1 as $player) {
|
||||||
|
$mu1 += $player->mu;
|
||||||
|
$sigmaSum += $player->sigma * $player->sigma;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mu2 = 0.0;
|
||||||
|
foreach ($team2 as $player) {
|
||||||
|
$mu2 += $player->mu;
|
||||||
|
$sigmaSum += $player->sigma * $player->sigma;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deltaMu = $mu1 - $mu2;
|
||||||
|
|
||||||
|
return NormalDistribution::getCdf(
|
||||||
|
($deltaMu - $drawMargin) / \sqrt((\count($team1) + \count($team2)) * ($this->beta * $this->beta) + $sigmaSum),
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw margin = epsilon
|
||||||
|
/**
|
||||||
|
* P_{draw} = 2\Phi\left(\dfrac{\epsilon}{\sqrt{n_1 + n_2} * \beta}\right) - 1
|
||||||
|
*/
|
||||||
|
public function drawProbability(float $drawMargin, int $n1, int $n2, float $beta)
|
||||||
|
{
|
||||||
|
return 2 * NormalDistribution::getCdf($drawMargin / (\sqrt($n1 + $n2) * $beta), 0.0, 1.0) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \epsilon = \Phi^{-1}\left(\dfrac{P_{draw} + 1}{2}\right) * \sqrt{n_1 + n_2} * \beta
|
||||||
|
*/
|
||||||
|
public function drawMargin(float $drawProbability, int $n1, int $n2, float $beta)
|
||||||
|
{
|
||||||
|
return NormalDistribution::getIcdf(($drawProbability + 1) / 2.0, 0.0, 1.0) * \sqrt($n1 + $n2) * $beta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mean additive truncated gaussion function "v" for wins
|
||||||
|
*
|
||||||
|
* @latex c = \sqrt{2 * \beta^2 + \sigma_{winner}^2 + \sigma_{loser}^2}
|
||||||
|
* @latex \mu_{winner} = \mu_{winner} + \dfrac{\sigma_{winner}^2}{c} * \nu \left(\dfrac{\mu_{winner} - \mu_{loser}}{c}, \dfrac{\epsilon}{c}\right)
|
||||||
|
* @latex \mu_{loser} = \mu_{loser} + \dfrac{\sigma_{loser}^2}{c} * \nu \left(\dfrac{\mu_{winner} - \mu_{loser}}{c}, \dfrac{\epsilon}{c}\right)
|
||||||
|
* @latex t = \dfrac{\mu_{winner} - \mu_{loser}}{c}
|
||||||
|
*
|
||||||
|
* @latex \nu = \dfrac{\mathcal{N}(t - \epsilon)}{\Phi(t - \epsilon)}
|
||||||
|
*
|
||||||
|
* @param float $t Difference winner and loser mu
|
||||||
|
* @param float $epsilon Draw margin
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function vWin(float $t, float $epsilon) : float
|
||||||
|
{
|
||||||
|
return NormalDistribution::getPdf($t - $epsilon, 0, 1.0) / NormalDistribution::getCdf($t - $epsilon, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mean additive truncated gaussion function "v" for draws
|
||||||
|
*
|
||||||
|
* @latex c = \sqrt{2 * \beta^2 + \sigma_{winner}^2 + \sigma_{loser}^2}
|
||||||
|
* @latex \mu_{winner} = \mu_{winner} + \dfrac{\sigma_{winner}^2}{c} * \nu \left(\dfrac{\mu_{winner} - \mu_{loser}}{c}, \dfrac{\epsilon}{c}\right)
|
||||||
|
* @latex \mu_{loser} = \mu_{loser} + \dfrac{\sigma_{loser}^2}{c} * \nu \left(\dfrac{\mu_{winner} - \mu_{loser}}{c}, \dfrac{\epsilon}{c}\right)
|
||||||
|
* @latex t = \dfrac{\mu_{winner} - \mu_{loser}}{c}
|
||||||
|
* @latex \dfrac{\mathcal{N}(t - \epsilon)}{\Phi(t - \epsilon)}
|
||||||
|
*
|
||||||
|
* @latex \nu = \dfrac{\mathcal{N}(-\epsilon - t) - \mathcal{N}(\epsilon - t)}{\Phi(\epsilon - t) - \Phi(-\epsilon - t)}
|
||||||
|
*
|
||||||
|
* @param float $t Difference winner and loser mu
|
||||||
|
* @param float $epsilon Draw margin
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function vDraw(float $t, float $epsilon) : float
|
||||||
|
{
|
||||||
|
$tAbs = \abs($t);
|
||||||
|
$a = $epsilon - $tAbs;
|
||||||
|
$b = -$epsilon - $tAbs;
|
||||||
|
|
||||||
|
$aPdf = NormalDistribution::getPdf($a, 0.0, 1.0);
|
||||||
|
$bPdf = NormalDistribution::getPdf($b, 0.0, 1.0);
|
||||||
|
$numer = $bPdf - $aPdf;
|
||||||
|
|
||||||
|
$aCdf = NormalDistribution::getCdf($a, 0.0, 1.0);
|
||||||
|
$bCdf = NormalDistribution::getCdf($b, 0.0, 1.0);
|
||||||
|
$denom = $aCdf - $bCdf;
|
||||||
|
|
||||||
|
return $numer / $denom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variance multiplicative function "w" for draws
|
||||||
|
*
|
||||||
|
* @latex w = \nu * (\nu + t - \epsilon)
|
||||||
|
*
|
||||||
|
* @param float $t Difference winner and loser mu
|
||||||
|
* @param float $epsilon Draw margin
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function wWin(float $t, float $epsilon) : float
|
||||||
|
{
|
||||||
|
$v = $this->vWin($t, $epsilon);
|
||||||
|
|
||||||
|
return $v * ($v + $t - $epsilon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Variance multiplicative function "w" for draws
|
||||||
|
*
|
||||||
|
* @latex w = \nu^2 + \dfrac{(\epsilon - t) * \mathcal{N}(\epsilon - t) + (\epsilon + t) * \mathcal{N}(\epsilon + t)}{\Phi(\epsilon - t) - \Phi(-\epsilon - t)}
|
||||||
|
*
|
||||||
|
* @param float $t Difference winner and loser mu
|
||||||
|
* @param float $epsilon Draw margin
|
||||||
|
*
|
||||||
|
* @return float
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private function wDraw(float $t, float $epsilon) : float
|
||||||
|
{
|
||||||
|
$tAbs = \abs($t);
|
||||||
|
|
||||||
|
$v = $this->vDraw($t, $epsilon);
|
||||||
|
|
||||||
|
return $v * $v
|
||||||
|
+ (($epsilon - $t) * NormalDistribution::getPdf($epsilon - $tAbs, 0.0, 1.0) + ($epsilon + $tAbs) * NormalDistribution::getPdf($epsilon + $tAbs, 0.0, 1.0))
|
||||||
|
/ (NormalDistribution::getCdf($epsilon - $tAbs, 0.0, 1.0) - NormalDistribution::getCdf(-$epsilon - $tAbs, 0.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRatingLayer() : void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildPerformanceLayer() : void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildTeamPerformanceLayer() : void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildTruncLayer() : void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private function factorGraphBuilders()
|
||||||
|
{
|
||||||
|
// Rating layer
|
||||||
|
|
||||||
|
// Performance layer
|
||||||
|
|
||||||
|
// Team Performance layer
|
||||||
|
|
||||||
|
// Trunc layer
|
||||||
|
|
||||||
|
return [
|
||||||
|
'rating_layer' => $ratingLayer,
|
||||||
|
'performance_layer' => $ratingLayer,
|
||||||
|
'team_performance_layer' => $ratingLayer,
|
||||||
|
'trunc_layer' => $ratingLayer,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rating() : void
|
||||||
|
{
|
||||||
|
// Start values
|
||||||
|
$mu = 25;
|
||||||
|
$sigma = $mu / 3;
|
||||||
|
$beta = $sigma / 2;
|
||||||
|
$tau = $sigma / 100;
|
||||||
|
$Pdraw = 0.1;
|
||||||
|
|
||||||
|
$alpha = 0.25;
|
||||||
|
|
||||||
|
// Partial update
|
||||||
|
$sigmaPartial = $sigmaOld * $sigmaNew / \sqrt($alpha * $sigmaOld * $sigmaOld - ($alpha - 1) * $sigmaNew * $sigmaNew);
|
||||||
|
$muPartial = $muOld * ($alpha - 1) * $sigmaNew * $sigmaNew - $muNew * $alpha * $sigmaOld * $sigmaOld
|
||||||
|
/ (($alpha - 1) * $sigmaNew * $sigmaNew - $alpha * $sigmaOld * $sigmaOld);
|
||||||
|
|
||||||
|
// New
|
||||||
|
$tau = $pi * $mu;
|
||||||
|
|
||||||
|
$P = NormalDistribution::getCdf(($s1 - $s2) / (\sqrt(2) * $beta));
|
||||||
|
$Delta = $alpha * $beta * \sqrt($pi) * (($y + 1) / 2 - $P);
|
||||||
|
|
||||||
|
$K = NormalDistribution::getCdf();
|
||||||
|
|
||||||
|
$pi = 1 / ($sigma * $sigma);
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Algorithm/Rating/TrueSkillFactoryGraph.php
Normal file
28
Algorithm/Rating/TrueSkillFactoryGraph.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Rating;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Elo rating calculation using Elo rating
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Rating
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @see https://en.wikipedia.org/wiki/Elo_rating_system
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class TrueSkillFactoryGraph
|
||||||
|
{
|
||||||
|
}
|
||||||
86
Algorithm/Sort/BitonicSort.php
Executable file
86
Algorithm/Sort/BitonicSort.php
Executable file
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BitonicSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class BitonicSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = self::sort(\array_slice($list, 0, (int) ($n / 2)), SortOrder::ASC);
|
||||||
|
$second = self::sort(\array_slice($list, (int) ($n / 2)), SortOrder::DESC);
|
||||||
|
|
||||||
|
return self::merge(\array_merge($first, $second), $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splitting, merging and sorting list
|
||||||
|
*
|
||||||
|
* @param array $list List to sort
|
||||||
|
* @param int $order Sort order
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function merge(array $list, int $order) : array
|
||||||
|
{
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n === 1) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dist = $n / 2;
|
||||||
|
for ($i = 0; $i < $dist; ++$i) {
|
||||||
|
if ($list[$i]->compare($list[$i + $dist], $order)) {
|
||||||
|
$old = $list[$i];
|
||||||
|
$list[$i] = $list[$i + $dist];
|
||||||
|
$list[$i + $dist] = $old;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$first = self::merge(\array_slice($list, 0, (int) ($n / 2)), $order);
|
||||||
|
$second = self::merge(\array_slice($list, (int) ($n / 2)), $order);
|
||||||
|
|
||||||
|
return \array_merge($first, $second);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
Algorithm/Sort/BubbleSort.php
Executable file
66
Algorithm/Sort/BubbleSort.php
Executable file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bubblesort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class BubbleSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
$newN = 0;
|
||||||
|
|
||||||
|
for ($i = 1; $i < $n; ++$i) {
|
||||||
|
if ($list[$i - 1]->compare($list[$i], $order)) {
|
||||||
|
$old = $list[$i - 1];
|
||||||
|
$list[$i - 1] = $list[$i];
|
||||||
|
$list[$i] = $old;
|
||||||
|
|
||||||
|
$newN = $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$n = $newN;
|
||||||
|
} while ($n > 1);
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
Algorithm/Sort/BucketSort.php
Executable file
63
Algorithm/Sort/BucketSort.php
Executable file
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bucketsort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class BucketSort
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sort array
|
||||||
|
*
|
||||||
|
* @param array $list List of sortable elements
|
||||||
|
* @param int $bucketCount Buckets to divide the list into
|
||||||
|
* @param string $algo Algorithm to use for sort
|
||||||
|
* @param int $order Sort order
|
||||||
|
*
|
||||||
|
* @return array Sorted array
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $bucketCount, string $algo = InsertionSort::class, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$buckets = [];
|
||||||
|
$M = $list[0]::max($list);
|
||||||
|
|
||||||
|
if ($bucketCount < 1) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\count($list) < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($list as $element) {
|
||||||
|
$buckets[(int) \floor(($bucketCount - 1) * $element->getValue() / $M)][] = $element;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sorted = [];
|
||||||
|
foreach ($buckets as $bucket) {
|
||||||
|
$sorted[] = $algo::sort($bucket, SortOrder::ASC);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $order === SortOrder::ASC ? \array_merge(...$sorted) : \array_reverse(\array_merge(...$sorted), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
80
Algorithm/Sort/CocktailShakerSort.php
Executable file
80
Algorithm/Sort/CocktailShakerSort.php
Executable file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CocktailShakerSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class CocktailShakerSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$start = 0;
|
||||||
|
$end = \count($list) - 1;
|
||||||
|
|
||||||
|
if ($end < 1) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($start <= $end) {
|
||||||
|
$newStart = $end;
|
||||||
|
$newEnd = $start;
|
||||||
|
|
||||||
|
for ($i = $start; $i < $end; ++$i) {
|
||||||
|
if ($list[$i]->compare($list[$i + 1], $order)) {
|
||||||
|
$old = $list[$i];
|
||||||
|
$list[$i] = $list[$i + 1];
|
||||||
|
$list[$i + 1] = $old;
|
||||||
|
|
||||||
|
$newEnd = $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$end = $newEnd - 1;
|
||||||
|
|
||||||
|
for ($i = $end; $i >= $start; --$i) {
|
||||||
|
if ($list[$i]->compare($list[$i + 1], $order)) {
|
||||||
|
$old = $list[$i];
|
||||||
|
$list[$i] = $list[$i + 1];
|
||||||
|
$list[$i + 1] = $old;
|
||||||
|
|
||||||
|
$newStart = $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$start = $newStart + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
||||||
75
Algorithm/Sort/CombSort.php
Executable file
75
Algorithm/Sort/CombSort.php
Executable file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CombSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class CombSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$sorted = false;
|
||||||
|
$n = \count($list);
|
||||||
|
$gap = $n;
|
||||||
|
$shrink = 1.3;
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!$sorted) {
|
||||||
|
$gap = (int) \floor($gap / $shrink);
|
||||||
|
|
||||||
|
if ($gap < 2) {
|
||||||
|
$gap = 1;
|
||||||
|
$sorted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
while ($i + $gap < $n) {
|
||||||
|
if ($list[$i]->compare($list[$i + $gap], $order)) {
|
||||||
|
$old = $list[$i];
|
||||||
|
$list[$i] = $list[$i + 1];
|
||||||
|
$list[$i + 1] = $old;
|
||||||
|
|
||||||
|
$sorted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
++$i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
Algorithm/Sort/CycleSort.php
Executable file
96
Algorithm/Sort/CycleSort.php
Executable file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CycleSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class CycleSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($start = 0; $start < $n - 1; ++$start) {
|
||||||
|
$item = $list[$start];
|
||||||
|
|
||||||
|
$pos = $start;
|
||||||
|
$length0 = \count($list);
|
||||||
|
for ($i = $start + 1; $i < $length0; ++$i) {
|
||||||
|
if (!$list[$i]->compare($item, $order)) {
|
||||||
|
++$pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pos === $start) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($item->equals($list[$pos])) {
|
||||||
|
++$pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pos !== $start) {
|
||||||
|
$old = $list[$pos];
|
||||||
|
$list[$pos] = $item;
|
||||||
|
$item = $old;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($pos !== $start) {
|
||||||
|
$pos = $start;
|
||||||
|
|
||||||
|
for ($i = $start + 1; $i < $n; ++$i) {
|
||||||
|
if (!$list[$i]->compare($item, $order)) {
|
||||||
|
++$pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (isset($list[$pos]) && $item->equals($list[$pos])) {
|
||||||
|
++$pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($list[$pos])) {
|
||||||
|
$old = $list[$pos];
|
||||||
|
$list[$pos] = $item;
|
||||||
|
$item = $old;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Algorithm/Sort/GnomeSort.php
Executable file
62
Algorithm/Sort/GnomeSort.php
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GnomeSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class GnomeSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; $i < $n; ++$i) {
|
||||||
|
$j = $i;
|
||||||
|
|
||||||
|
while ($j > 0 && $list[$j - 1]->compare($list[$j], $order)) {
|
||||||
|
$old = $list[$j - 1];
|
||||||
|
$list[$j - 1] = $list[$j];
|
||||||
|
$list[$j] = $old;
|
||||||
|
|
||||||
|
--$j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
||||||
98
Algorithm/Sort/HeapSort.php
Executable file
98
Algorithm/Sort/HeapSort.php
Executable file
|
|
@ -0,0 +1,98 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HeapSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class HeapSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
$copy = $list;
|
||||||
|
|
||||||
|
for ($p = (int) ($n / 2 - 1); $p >= 0; --$p) {
|
||||||
|
self::heapify($copy, $n, $p, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = $n - 1; $i > 0; --$i) {
|
||||||
|
$temp = $copy[$i];
|
||||||
|
$copy[$i] = $copy[0];
|
||||||
|
$copy[0] = $temp;
|
||||||
|
|
||||||
|
--$n;
|
||||||
|
self::heapify($copy, $n, 0, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert into heap data structure
|
||||||
|
*
|
||||||
|
* @param array $list Data to sort
|
||||||
|
* @param int $size Heap size
|
||||||
|
* @param int $index Index element
|
||||||
|
* @param int $order Sort order
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function heapify(array &$list, int $size, int $index, int $order) : void
|
||||||
|
{
|
||||||
|
$left = ($index + 1) * 2 - 1;
|
||||||
|
$right = ($index + 1) * 2;
|
||||||
|
$pivot = 0;
|
||||||
|
|
||||||
|
$pivot = $left < $size && $list[$left]->compare($list[$index], $order) ? $left : $index;
|
||||||
|
|
||||||
|
if ($right < $size && $list[$right]->compare($list[$pivot], $order)) {
|
||||||
|
$pivot = $right;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pivot !== $index) {
|
||||||
|
$temp = $list[$index];
|
||||||
|
$list[$index] = $list[$pivot];
|
||||||
|
$list[$pivot] = $temp;
|
||||||
|
|
||||||
|
self::heapify($list, $size, $pivot, $order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Algorithm/Sort/InsertionSort.php
Executable file
62
Algorithm/Sort/InsertionSort.php
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InsertionSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class InsertionSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 1; $i < $n; ++$i) {
|
||||||
|
$pivot = $list[$i];
|
||||||
|
$j = $i - 1;
|
||||||
|
|
||||||
|
while ($j >= 0 && $list[$j]->compare($pivot, $order)) {
|
||||||
|
$list[$j + 1] = $list[$j];
|
||||||
|
--$j;
|
||||||
|
}
|
||||||
|
|
||||||
|
$list[$j + 1] = $pivot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
Algorithm/Sort/IntroSort.php
Executable file
88
Algorithm/Sort/IntroSort.php
Executable file
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IntroSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class IntroSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$clone = $list;
|
||||||
|
$size = self::partition($clone, 0, \count($list) - 1, $order);
|
||||||
|
|
||||||
|
if ($size < 16) {
|
||||||
|
return InsertionSort::sort($clone, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($size > \log(\count($list)) * 2) {
|
||||||
|
return HeapSort::sort($clone, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QuickSort::sort($clone);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partition list and return the size
|
||||||
|
*
|
||||||
|
* @param array $list List reference
|
||||||
|
* @param int $lo Low or left side
|
||||||
|
* @param int $hi High or right side
|
||||||
|
* @param int $order Order type
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function partition(array &$list, int $lo, int $hi, int $order) : int
|
||||||
|
{
|
||||||
|
$pivot = $list[$hi];
|
||||||
|
$i = $lo;
|
||||||
|
|
||||||
|
for ($j = $lo; $j < $hi; ++$j) {
|
||||||
|
if ($list[$j]->compare($pivot, $order)) {
|
||||||
|
$temp = $list[$j];
|
||||||
|
$list[$j] = $list[$i];
|
||||||
|
$list[$i] = $temp;
|
||||||
|
|
||||||
|
++$i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$list[$hi] = $list[$i];
|
||||||
|
$list[$i] = $pivot;
|
||||||
|
|
||||||
|
return $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
137
Algorithm/Sort/MergeSort.php
Executable file
137
Algorithm/Sort/MergeSort.php
Executable file
|
|
@ -0,0 +1,137 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MergeSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class MergeSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
$clone = $list;
|
||||||
|
self::sortHalve($clone, 0, $n - 1, $order);
|
||||||
|
|
||||||
|
return $clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursive sorting of halve of the list and then merging it
|
||||||
|
*
|
||||||
|
* @param array $list Data to sort
|
||||||
|
* @param int $lo Start of the list to sort
|
||||||
|
* @param int $hi End of the list to sort
|
||||||
|
* @param int $order Sort order
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function sortHalve(array &$list, int $lo, int $hi, int $order) : void
|
||||||
|
{
|
||||||
|
if ($lo >= $hi) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mi = (int) ($lo + ($hi - $lo) / 2);
|
||||||
|
|
||||||
|
self::sortHalve($list, $lo, $mi, $order);
|
||||||
|
self::sortHalve($list, $mi + 1, $hi, $order);
|
||||||
|
|
||||||
|
self::merge($list, $lo, $mi, $hi, $order);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge and sort sub list
|
||||||
|
*
|
||||||
|
* @param array $list Data to sort
|
||||||
|
* @param int $lo Start of the list to sort
|
||||||
|
* @param int $mi Middle point of the list to sort
|
||||||
|
* @param int $hi End of the list to sort
|
||||||
|
* @param int $order Sort order
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
private static function merge(array &$list, int $lo, int $mi, int $hi, int $order) : void
|
||||||
|
{
|
||||||
|
$n1 = $mi - $lo + 1;
|
||||||
|
$n2 = $hi - $mi;
|
||||||
|
|
||||||
|
$loList = [];
|
||||||
|
$hiList = [];
|
||||||
|
|
||||||
|
for ($i = 0; $i < $n1; ++$i) {
|
||||||
|
$loList[$i] = $list[$lo + $i];
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < $n2; ++$i) {
|
||||||
|
$hiList[$i] = $list[$mi + 1 + $i];
|
||||||
|
}
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$j = 0;
|
||||||
|
$k = $lo;
|
||||||
|
|
||||||
|
while ($i < $n1 && $j < $n2) {
|
||||||
|
if (!$loList[$i]->compare($hiList[$j], $order)) {
|
||||||
|
$list[$k] = $loList[$i];
|
||||||
|
++$i;
|
||||||
|
} else {
|
||||||
|
$list[$k] = $hiList[$j];
|
||||||
|
++$j;
|
||||||
|
}
|
||||||
|
|
||||||
|
++$k;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($i < $n1) {
|
||||||
|
$list[$k] = $loList[$i];
|
||||||
|
++$i;
|
||||||
|
++$k;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ($j < $n2) {
|
||||||
|
$list[$k] = $hiList[$j];
|
||||||
|
++$j;
|
||||||
|
++$k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
Algorithm/Sort/OddEvenSort.php
Executable file
75
Algorithm/Sort/OddEvenSort.php
Executable file
|
|
@ -0,0 +1,75 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Jingga
|
||||||
|
*
|
||||||
|
* PHP Version 8.2
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @copyright Dennis Eichhorn
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @version 1.0.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace phpOMS\Algorithm\Sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OddEvenSort class.
|
||||||
|
*
|
||||||
|
* @package phpOMS\Algorithm\Sort;
|
||||||
|
* @license OMS License 2.0
|
||||||
|
* @link https://jingga.app
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
final class OddEvenSort implements SortInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function sort(array $list, int $order = SortOrder::ASC) : array
|
||||||
|
{
|
||||||
|
$sorted = false;
|
||||||
|
$n = \count($list);
|
||||||
|
|
||||||
|
if ($n < 2) {
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!$sorted) {
|
||||||
|
$sorted = true;
|
||||||
|
|
||||||
|
for ($i = 1; $i < $n - 1; $i += 2) {
|
||||||
|
if ($list[$i]->compare($list[$i + 1], $order)) {
|
||||||
|
$old = $list[$i];
|
||||||
|
$list[$i] = $list[$i + 1];
|
||||||
|
$list[$i + 1] = $old;
|
||||||
|
|
||||||
|
$sorted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i < $n - 1; $i += 2) {
|
||||||
|
if ($list[$i]->compare($list[$i + 1], $order)) {
|
||||||
|
$old = $list[$i];
|
||||||
|
$list[$i] = $list[$i + 1];
|
||||||
|
$list[$i + 1] = $old;
|
||||||
|
|
||||||
|
$sorted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user