penjadwalan-ga/app/Services/Scheduling/FitnessEvaluator.php

104 lines
3.7 KiB
PHP

<?php
namespace App\Services\Scheduling;
use App\Services\Scheduling\Data\SchedulingDataset;
/**
* FitnessEvaluator mengevaluasi 7 Hard Constraints (B1 - B7) pada sebuah Chromosome.
* Setiap pelanggaran aturan menambah penalti +1.
*
* Fitness = 1.0 / (1.0 + Total Penalty)
*/
class FitnessEvaluator
{
public function evaluate(Chromosome $chromosome, SchedulingDataset $dataset): void
{
$breakdown = [
'B1' => 0, // Bentrok Guru
'B2' => 0, // Bentrok Ruangan
'B3' => 0, // Ketersediaan Guru
'B4' => 0, // Bentrok Kelas
'B5' => 0, // Kualifikasi Level
'B6' => 0, // Sebaran Hari
'B7' => 0, // Konsistensi Guru
];
// Struktur pembantu untuk deteksi bentrok
// key guruSlot: "guruId_hariId_sesiId" -> count
$guruSlotMap = [];
// key ruanganSlot: "ruanganId_hariId_sesiId" -> count (khusus ruangan reguler LKP)
$ruanganSlotMap = [];
// key kelasSlot: "kelasId_hariId_sesiId" -> count
$kelasSlotMap = [];
// grouping gen per kelasId -> Gene[]
$kelasGenesMap = [];
foreach ($chromosome->genes as $gene) {
$kelas = $dataset->getKelas($gene->kelasId);
$guru = $dataset->getGuru($gene->guruId);
// B1: Bentrok Guru (1 Guru mengajar >1 kelas di (H, S) sama)
$guruSlotKey = "{$gene->guruId}_{$gene->hariId}_{$gene->sesiId}";
if (isset($guruSlotMap[$guruSlotKey])) {
$breakdown['B1']++;
} else {
$guruSlotMap[$guruSlotKey] = true;
}
// B2: Bentrok Ruangan (1 Ruangan LKP dipakai >1 kelas di (H, S) sama, khusus reguler)
if (!$kelas->isEkstrakurikuler) {
$ruanganSlotKey = "{$gene->ruanganId}_{$gene->hariId}_{$gene->sesiId}";
if (isset($ruanganSlotMap[$ruanganSlotKey])) {
$breakdown['B2']++;
} else {
$ruanganSlotMap[$ruanganSlotKey] = true;
}
}
// B3: Ketersediaan Guru (Guru diplot pada (H, S) yang TIDAK terdaftar di ketersediaannya)
if (!$guru->tersediaAt(new \App\Services\Scheduling\Data\SchedulingSlot($gene->hariId, $gene->sesiId))) {
$breakdown['B3']++;
}
// B4: Bentrok Kelas (1 Kelas diplot di (H, S) yang sama)
$kelasSlotKey = "{$gene->kelasId}_{$gene->hariId}_{$gene->sesiId}";
if (isset($kelasSlotMap[$kelasSlotKey])) {
$breakdown['B4']++;
} else {
$kelasSlotMap[$kelasSlotKey] = true;
}
// B5: Kualifikasi Level (Level Maksimal Guru < Level Program Kelas)
if ($guru->levelMaksimal < $kelas->level) {
$breakdown['B5']++;
}
// Grouping per kelas untuk B6 dan B7
$kelasGenesMap[$gene->kelasId][] = $gene;
}
// Evaluasi B6 & B7 (Khusus kelas 2x per minggu)
foreach ($kelasGenesMap as $kelasId => $genes) {
if (count($genes) == 2) {
$g1 = $genes[0];
$g2 = $genes[1];
// B6: Sebaran Hari (Hari tidak boleh sama, dan harus berjarak minimal 2 hari)
$selisihHari = abs($g1->hariId - $g2->hariId);
if ($g1->hariId == $g2->hariId || $selisihHari < 2) {
$breakdown['B6']++;
}
// B7: Konsistensi Guru (Guru pada kedua pertemuan harus sama)
if ($g1->guruId != $g2->guruId) {
$breakdown['B7']++;
}
}
}
$totalPenalty = array_sum($breakdown);
$chromosome->setEvaluationResult($totalPenalty, $breakdown);
}
}