-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f742a34
commit 48ebe37
Showing
5 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
const Schedule = require('./Schedule'); | ||
|
||
function mapNum (n, in_min, in_max, out_min, out_max) { | ||
return (n - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; | ||
} | ||
|
||
function getRndInteger(min, max) { | ||
return Math.floor(Math.random() * (max - min) ) + min; | ||
} | ||
|
||
module.exports = function Population(classCodes, mutationRate, num) { | ||
this.population = []; | ||
this.matingPool = []; | ||
this.generations = 0; | ||
this.finished = false; | ||
|
||
this.mutationRate = mutationRate; | ||
this.perfectScore = 1; | ||
|
||
for(var i=0; i<num; i++) { | ||
this.population[i] = new Schedule(classCodes, true); | ||
} | ||
|
||
// Calculate fitness of each schedule | ||
this.calcFitness = function() { | ||
for(var i=0; i<this.population.length;i++) { | ||
this.population[i].calcFitness(); | ||
} | ||
} | ||
this.calcFitness(); | ||
|
||
this.naturalSelection = function() { | ||
this.matingPool = []; | ||
|
||
var maxFitness = 0; | ||
for(var i=0; i<this.population.length;i++) { | ||
if(this.population[i].fitness > maxFitness) { | ||
maxFitness = this.population[i].fitness; | ||
} | ||
} | ||
|
||
for(var i=0; i<this.population.length;i++) { | ||
var fitness = mapNum(this.population[i].fitness,0,maxFitness,0,1); | ||
var n = Math.floor(fitness*100); | ||
for(var j=0;j<n;j++) { | ||
this.matingPool.push(i); | ||
} | ||
} | ||
} | ||
|
||
this.generate = function() { | ||
var newPop = []; | ||
for(var i=0; i<this.population.length;i++) { | ||
var a = getRndInteger(0,this.matingPool.length); | ||
var b = getRndInteger(0, this.matingPool.length); | ||
var partnerA = this.population[this.matingPool[a]]; | ||
var partnerB = this.population[this.matingPool[b]]; | ||
var child = partnerA.crossover(partnerB); | ||
child.mutate(this.mutationRate); | ||
newPop.push(child); | ||
} | ||
this.population = newPop; | ||
this.generations++; | ||
} | ||
|
||
this.getBest = function() { | ||
return this.best; | ||
} | ||
|
||
this.evaluate = function() { | ||
var record = 0.0; | ||
var index = 0; | ||
|
||
for(var i=0; i < this.population.length; i++) { | ||
if(this.population[i].fitness > record) { | ||
index = i; | ||
record = this.population[i].fitness; | ||
} | ||
} | ||
|
||
this.best = this.population[index]; | ||
if(record === this.perfectScore) { | ||
this.finished = true; | ||
} | ||
} | ||
|
||
this.isFinished = function() { | ||
return this.finished; | ||
} | ||
|
||
this.getGenerations = function() { | ||
return this.generations; | ||
} | ||
|
||
this.print = function() { | ||
for(var i=0; i<this.population.length;i++) { | ||
this.population[i].print(); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
const oferta = require('./oferta.json')['oferta']; // oferta is auto generated by scraper | ||
|
||
// format { | ||
// CBF210: { | ||
// name: "", | ||
// schedule: { | ||
// lunes: { | ||
// desde: 0, | ||
// hasta: 1 | ||
// } | ||
// ... | ||
// } | ||
// } | ||
// ... | ||
// } | ||
|
||
function getRndInteger(min, max) { | ||
return Math.floor(Math.random() * (max - min) ) + min; | ||
} | ||
|
||
function selectRandomSection(code) { | ||
if(!oferta[code]) { | ||
|
||
throw "COULD NOT FIND OFFER FOR " + code; | ||
} | ||
if(oferta[code].secciones.length > 0) { | ||
return oferta[code].secciones[getRndInteger(0, oferta[code].secciones.length)]; | ||
} | ||
return null; | ||
} | ||
|
||
function checkDesiredSectionConflict(curr, desiredSect) { | ||
if(!desiredSect || (desiredSect && desiredSect.includes(curr.sec))) { | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
function checkDayConflict(day, curr, pool, log) { | ||
let myTime = curr.schedule[day]; | ||
if(myTime) { | ||
for(let i=0; i<pool.length; i++) { | ||
let otherTime = pool[i].schedule[day]; | ||
if(log) { | ||
console.log('CHECK CONFLICTS'); | ||
console.log('me',myTime); | ||
console.log('oth',otherTime); | ||
} | ||
if(otherTime) { | ||
if(myTime.desde < otherTime.hasta && otherTime.desde < myTime.hasta) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
const days = ['lunes', 'martes', 'miercoles', 'jueves', 'viernes', 'sabado']; | ||
|
||
module.exports = function Schedule(classCodes, prefill) { | ||
this.classCodes = classCodes; | ||
this.sections = []; | ||
this.fitness = 0; | ||
|
||
if(prefill) { | ||
for(let i=0; i<classCodes.length; i++) { | ||
|
||
this.sections[i] = selectRandomSection(classCodes[i].code); | ||
} | ||
} | ||
|
||
this.getCredits = function() { | ||
let credits = 0; | ||
for(let i=0; i<this.classCodes.length; i++) { | ||
let classData = oferta[this.classCodes[i].code]; | ||
if(classData.credits) { | ||
credits+= classData.credits; | ||
} | ||
} | ||
|
||
return credits; | ||
} | ||
|
||
this.calcFitness = function(log) { | ||
let conflicts = 0; | ||
|
||
for(let i=0; i<this.sections.length; i++) { | ||
let sect = this.sections[i]; | ||
let desiredSect = this.classCodes[i].section; | ||
|
||
let hasDesiredSect = checkDesiredSectionConflict(sect, desiredSect); | ||
if(!hasDesiredSect) { | ||
conflicts++; | ||
} | ||
|
||
for(let j=0; j<days.length;j++) { | ||
if(log) { | ||
console.log('check if has conflict for day ' + days[j]); | ||
} | ||
let hasConflict = checkDayConflict(days[j], sect, this.sections.slice(i+1), log); | ||
|
||
if(hasConflict) | ||
conflicts++; | ||
|
||
} | ||
} | ||
|
||
// Calculate score based on MAX number of conflicts - this one's number of conficts | ||
let score = this.sections.length*2 - conflicts; | ||
this.fitness = score/(this.sections.length*2); | ||
|
||
if(log) { | ||
console.log('CONFLICTS', conflicts); | ||
console.log('sections', this.sections.length); | ||
console.log('score', score); | ||
console.log('fitness', this.fitness); | ||
} | ||
} | ||
|
||
this.crossover = function(partner) { | ||
let child = new Schedule(this.classCodes); | ||
|
||
// For crossover, for each class we will have | ||
// a 50% chance of picking the section from either parent. | ||
let swapProbability = 0.5; | ||
for(let i=0; i<this.sections.length;i++) { | ||
if(Math.random() < swapProbability) { | ||
child.sections[i] = this.sections[i]; | ||
} else { | ||
child.sections[i] = partner.sections[i]; | ||
} | ||
} | ||
|
||
return child; | ||
} | ||
|
||
this.mutate = function(mutationRate) { | ||
// For each section, there's a mutationRate chance that it will get | ||
// replaced by another random section (of the same class) | ||
for(let i=0; i<this.sections.length; i++) { | ||
if(Math.random() < mutationRate) { | ||
this.sections[i] = selectRandomSection(this.classCodes[i].code); | ||
} | ||
} | ||
} | ||
|
||
this.print = function() { | ||
for(let i=0; i<this.sections.length; i++) { | ||
let sect = this.sections[i]; | ||
console.log(oferta[this.classCodes[i].code].name + '\r\n-- ' + sect.sec + ' ' + sect.prof); | ||
for(let j=0;j<days.length;j++) { | ||
if(sect.schedule[days[j]]) { | ||
console.log('---- ' + days[j] + ' ' + sect.schedule[days[j]].desde + '-' + sect.schedule[days[j]].hasta); | ||
} | ||
|
||
} | ||
console.log(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
const Population = require('./Population'); | ||
|
||
var selection = [ | ||
// {code: "CBM203", section: ["06"]}, Force a section | ||
// {code: "ING210", section: ["01", "06", "07", "08"]}, Select from specific sections | ||
// {code: "IDS336"}, // Select from available sections | ||
|
||
{code: "ADM315"}, // Administracion y Gestion empresarial | ||
{code: "IDS335"}, // Diseño de software | ||
{code: "INS380"}, // Base de datos II | ||
{code: "INS380L"}, // Lab base de datos II | ||
{code: "INS314", section: ["01"]}, // Comunicacion de Datos I | ||
{code: "INS314", section: ["71"]} // Lab comun datos I | ||
]; | ||
|
||
const mutationRate = 0.01; | ||
const popMax = 50; | ||
const maxGenerations = 1000; // When this number is reached, the best to date will be selected | ||
// (it usually means the schedule was impossible to generate without conflicts) | ||
|
||
var population = new Population(selection, mutationRate, popMax); | ||
|
||
while(!population.isFinished()) { | ||
population.naturalSelection(); | ||
population.generate(); | ||
population.calcFitness(); | ||
population.evaluate(); | ||
console.log('Generation ' + population.getGenerations()); | ||
|
||
if(maxGenerations && population.getGenerations() == maxGenerations) { | ||
console.log('FAILED. REACHED MAX NUMBER OF GENERATIONS. THE BEST FOUND TO DATE WILL BE PRINTED.'); | ||
break; | ||
} | ||
} | ||
|
||
console.log('DONE!'); | ||
population.getBest().print(); | ||
console.log('Credits: ' + population.getBest().getCredits()); | ||
console.log('Winning fitness: ' + population.getBest().fitness); | ||
|
||
|
||
|
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"name": "schedule-builder", | ||
"version": "1.0.0", | ||
"description": "Tool to build INTEC schedule based on offer generated by the scraper", | ||
"main": "app.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1", | ||
"start": "node app.js" | ||
}, | ||
"author": "Pedro Lopez", | ||
"license": "ISC" | ||
} |