public TestniPodaci(string nazivDatoteke) { this.nazivDatoteke = nazivDatoteke; using (var mappedFile1 = MemoryMappedFile.CreateFromFile(nazivDatoteke)) { using (Stream mmStream = mappedFile1.CreateViewStream()) { using (StreamReader sr = new StreamReader(mmStream, Encoding.Default)) { sr.ReadLine(); sr.ReadLine(); sr.ReadLine(); var line = sr.ReadLine(); var lineWords = line.Split(' '); brojVrhova = Convert.ToInt32(lineWords[2]); sr.ReadLine(); line = sr.ReadLine(); lineWords = line.Split(' '); if (lineWords[0] == "CAPACITY") kapacitetVozila = Convert.ToInt32(lineWords[2]); vrhovi = new Vrh[brojVrhova + 1]; for (int i = 0; i < brojVrhova + 1; i++) { vrhovi[i] = new Vrh(); } while (true) { line = sr.ReadLine(); lineWords = line.Split(' '); if (lineWords[0] == "NODE_COORD_SECTION") { for (int i = 1; i <= brojVrhova; i++) { var line2 = sr.ReadLine(); var lineWords2 = line2.Split(' '); vrhovi[i].x = Convert.ToInt32(lineWords2[2]); vrhovi[i].y = Convert.ToInt32(lineWords2[3]); } } if (lineWords[0] == "DEMAND_SECTION") { for (int i = 1; i <= brojVrhova; i++) { line = sr.ReadLine(); lineWords = line.Split(' '); vrhovi[i].potraznja = Convert.ToInt32(lineWords[1]); vrhovi[i].oznaka = i; } vrhovi[0].potraznja = 0; vrhovi[0].oznaka = 0; } if (lineWords[0] == "EOF") break; } } } } }
/* ako je smijeNula = 0, funkcija nikad ne vraca 0, sto nam ponekad treba jer ne smijemo dijeliti s nula i sl. */ public double udaljenost(Vrh drugiVrh, int smijeNula = 1) { double udaljeni; udaljeni = Math.Round(Math.Sqrt(Math.Pow(this.x - drugiVrh.x, 2) + Math.Pow(this.y - drugiVrh.y, 2))); if (udaljeni == 0 && smijeNula == 0) return 0.000000001; else return udaljeni; }
// nije doslovce 2-opt, tj. prikladniji naziv bi mozda bio kvaziDvaOpt... ipak, trebalo bi raditi *vise* od pravog dvaOpta (ali sporije) // pokusa zamijeniti *svaka* dva vrha u obilasku, cak ako su isti ili zamjena nije dopustiva itd. a onda gleda je li ta zamjena dopustiva // pa na kraju medju svim dopustivim zamjenama vraca onu koja najvise poboljsava rjesenje AKO ga ijedna poboljsava. // moze se poboljsati tako da ima manje *potpuno* nepotrebnih operacija... // treci parametar za sada neiskoristen, moze se iskoristiti za to da gledamo zamjene samo najblizih vrhova, ali // to moze dovesti do toga da funkcija ne nadje neko bolje rjesenje koje je mogla. prednost bi bila povecanje brzine, ali za sada je // ipak kvaliteta rjesenja prioritet... public Obilazak dvaOpt(double dopustenaCijena, double ulaznaDuljina, Dictionary <Vrh, List <Vrh> > najbliziVrhovi = null) { if (put == null) { return(null); } Obilazak obilazak; Obilazak izlazniObilazak = null; List <Obilazak> listaObilazaka = new List <Obilazak>(); for (int i = 1; i < put.Count(); i++) { for (int j = i; j < put.Count(); j++) { { obilazak = new Obilazak(); foreach (var vrh in put) { obilazak.dodajVrh(vrh); } Vrh temp = obilazak.put[i]; obilazak.put[i] = obilazak.put[j]; obilazak.put[j] = temp; if (obilazak.dopustiv(dopustenaCijena)) { listaObilazaka.Add(obilazak); } } } } double najboljaDuljina = ulaznaDuljina; foreach (var ob in listaObilazaka) { if (ob.duljinaObilaska() < najboljaDuljina) { najboljaDuljina = ob.duljinaObilaska(); izlazniObilazak = ob; } } if (najboljaDuljina < ulaznaDuljina) { return(izlazniObilazak); } else { return(null); } }
/* ako je smijeNula = 0, funkcija nikad ne vraca 0, sto nam ponekad treba jer ne smijemo dijeliti s nula i sl. */ public double udaljenost(Vrh drugiVrh, int smijeNula = 1) { double udaljeni; udaljeni = Math.Round(Math.Sqrt(Math.Pow(this.x - drugiVrh.x, 2) + Math.Pow(this.y - drugiVrh.y, 2))); if (udaljeni == 0 && smijeNula == 0) { return(0.000000001); } else { return(udaljeni); } }
public void dodajVrh(Vrh vrh) { put.Add(vrh); }
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) { long kolikoIteracija = Convert.ToInt32(textBox7.Text); string fileName = textBox8.Text; double alfa = Convert.ToDouble(textBox1.Text); double beta = Convert.ToDouble(textBox2.Text); double gama = Convert.ToDouble(textBox3.Text); double lambda = Convert.ToDouble(textBox5.Text); int brojMrava = Convert.ToInt32(textBox4.Text); double parametarEvaporacije = Convert.ToDouble(textBox6.Text.Split('.').ElementAt(1)) / 10; // ucitavanje testnih podataka TestniPodaci podaci = new TestniPodaci(fileName); int brojVrhova = podaci.brojVrhova; Vrh[] vrhovi = new Vrh[brojVrhova + 1]; double kapacitetVozila = podaci.kapacitetVozila; for (int i = 0; i <= brojVrhova; ++i) { vrhovi[i] = podaci.vrhovi[i]; } double[,] feromoni = new double[brojVrhova + 1, brojVrhova + 1]; double[,] eta = new double[brojVrhova + 1, brojVrhova + 1]; //dodani parametri mi i ka, objasnjeno u dokumentu "improvedVRP" double[,] mi = new double[brojVrhova + 1, brojVrhova + 1]; double[,] ka = new double[brojVrhova + 1, brojVrhova + 1]; double miMin = 0.2; double miMax = 0.9; /* svakom vrhu pridruzujemo listu 15 najblizih vrhova i spremamo te podatke u rjecnik * najbliziVrhovi. * za sad iskoristeno samo kod trazenja mogucih vrhova, vidi popunjavanje liste moguciVrhovi */ Dictionary<Vrh, List<Vrh>> najbliziVrhovi = new Dictionary<Vrh, List<Vrh>>(); foreach (var vrh in vrhovi) { najbliziVrhovi[vrh] = vrh.vratiNajblize(vrhovi.ToList(), 15); } for (int i = 0; i <= brojVrhova; i++) { for (int j = 0; j <= brojVrhova; j++) { feromoni[i, j] = 0.001; feromoni[j, i] = 0.001; eta[i, j] = 1 / (vrhovi[i].udaljenost(vrhovi[j], 0)); mi[i, j] = (vrhovi[i].udaljenost(vrhovi[1]) + vrhovi[1].udaljenost(vrhovi[j]) - vrhovi[i].udaljenost(vrhovi[j])) / 100; if (mi[i, j] < miMin) mi[i, j] = miMin; if (mi[i, j] > miMax) mi[i, j] = miMax; ka[i, j] = (vrhovi[i].potraznja + vrhovi[j].potraznja) / kapacitetVozila; } } Random random = new Random(); int brojIteracije = 1; int boljeRjesenjePrijeKoliko = 0; Obilazak globalniNajboljiPut = new Obilazak(); double globalnaMinDuljina = 1000000; // svaki prolazak kroz sljedecu petlju predstavlja jednu iteraciju algoritma while (brojIteracije - 1 < kolikoIteracija || kolikoIteracija < 0) { double[] ukupniPut = new double[brojMrava]; Obilazak[] prijedeniPut = new Obilazak[brojMrava]; // svaki prolazak kroz sljedecu petlju izvrsava posao jednog mrava for (int mrav = 0; mrav < brojMrava; ++mrav) { List<Vrh> neposjeceniVrhovi = new List<Vrh>(); prijedeniPut[mrav] = new Obilazak(); int brojPotpunoSlucajnih = 0; foreach (var vrh in vrhovi) { if (vrh.oznaka != 0) neposjeceniVrhovi.Add(vrh); } neposjeceniVrhovi.Remove(vrhovi[1]); prijedeniPut[mrav].dodajVrh(vrhovi[1]); // vrhovi[1] = skladiste, odatle pocinje svaki obilazak // slijedi konstrukcija kompletnog rjesenja... (to svaki mrav radi) velikaPetlja: while (neposjeceniVrhovi.Count() > 0) { int brojPocetnogVrha = random.Next(0, neposjeceniVrhovi.Count()); Vrh pocetniVrh = neposjeceniVrhovi[brojPocetnogVrha]; Vrh prosliVrh = pocetniVrh; double preostaliKapacitet = kapacitetVozila - prosliVrh.potraznja; prijedeniPut[mrav].dodajVrh(pocetniVrh); neposjeceniVrhovi.Remove(pocetniVrh); while (true) { List<Vrh> moguciVrhovi = new List<Vrh>(); foreach (var vrh in neposjeceniVrhovi) { if (preostaliKapacitet - vrh.potraznja >= 0 && vrh.oznaka != prosliVrh.oznaka && najbliziVrhovi[prosliVrh].Contains(vrh)) moguciVrhovi.Add(vrh); } if (moguciVrhovi.Count() == 0) { foreach (var vrh in neposjeceniVrhovi) { if (preostaliKapacitet - vrh.potraznja >= 0 && vrh.oznaka != prosliVrh.oznaka) moguciVrhovi.Add(vrh); } } double qParametar = random.NextDouble(); double q = 0.6; double q2 = 1 / ((brojPotpunoSlucajnih * 5) / brojVrhova + 6); // ako mrav vise ne moze prijeci ni u jedan vrh a da ne dostavi vise nego sto ima, krece opet iz skladista if (moguciVrhovi.Count() == 0) { prijedeniPut[mrav].dodajVrh(vrhovi[1]); goto velikaPetlja; }; Vrh sljedeciVrh = null; // imamo dva moguca nacina za izbor sljedeceg vrha, tj. po dvije razlicite formule // (vidi ANT COLONY SYSTEM, npr. u stuzle-99) i slucajno biramo na koji cemo od ta dva nacina // (za to sluze qParametar i q) // UPDATE: dodan treci nacin: potpuno slucajan izbor! (za potrebe toga dodan parametar q2) if (qParametar > q) { double r = random.NextDouble(); double[] vjerojatnosti = new double[brojVrhova + 1]; double[] rasponOd = new double[brojVrhova + 1]; double[] rasponDo = new double[brojVrhova + 1]; double suma = 0; foreach (var vrh in moguciVrhovi) { suma += (Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta)) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda); } foreach (var vrh in moguciVrhovi) { vjerojatnosti[vrh.oznaka] = (Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta)) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda) / suma; } double rasponDoProsli = 0; foreach (var vrh in moguciVrhovi) { rasponOd[vrh.oznaka] = rasponDoProsli; rasponDo[vrh.oznaka] = rasponOd[vrh.oznaka] + vjerojatnosti[vrh.oznaka]; rasponDoProsli = rasponDo[vrh.oznaka]; } rasponDo[moguciVrhovi[moguciVrhovi.Count() - 1].oznaka] = 1; double randomDouble = random.NextDouble(); foreach (var vrh in moguciVrhovi) { if (randomDouble >= rasponOd[vrh.oznaka] && randomDouble <= rasponDo[vrh.oznaka]) { sljedeciVrh = vrh; break; } } } else if (qParametar > q2) { double najvecaVrijednost = 0; foreach (var vrh in moguciVrhovi) { double vrijednostZaOvajVrh = Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda); if (vrijednostZaOvajVrh >= najvecaVrijednost) { najvecaVrijednost = vrijednostZaOvajVrh; sljedeciVrh = vrh; } } } else { brojPotpunoSlucajnih++; int indeksSljedeceg = random.Next(0, moguciVrhovi.Count()); sljedeciVrh = moguciVrhovi[indeksSljedeceg]; } preostaliKapacitet -= sljedeciVrh.potraznja; prijedeniPut[mrav].dodajVrh(sljedeciVrh); neposjeceniVrhovi.Remove(sljedeciVrh); } } // mrav je konstruirao neko rjesenje, slijedi dosta agresivno i vremenski skupo lokalno pretrazivanje // stavljeno je da se vrsi samo u svakoj desetoj iteraciji jer bi inace bilo jos puno sporije // ako se zakomentira taj dio koda, ide puno brze, ali i rezultati su losiji // EDIT: dodano da program pamti do sada poznata poboljsanja i onda ne pretrazuje ako smo za neki put vec prije vrsili // dvaOpt ili triOpt... medutim, cini se da to ne ubrzava program (mozda nesto nije dobro napravljeno?)... ali ga ni ne // usporava... znaci, to sad nije toliko bitno, prouciti kasnije. // if (brojIteracije % 20 == 10) if (mrav == (int)( ( (double)brojIteracije / kolikoIteracija) * brojMrava)) { Obilazak ulazniObilazak = new Obilazak(); foreach (var vrh in prijedeniPut[mrav].put) { ulazniObilazak.dodajVrh(vrh); } int p = 1; while (p == 1) { p = 0; Obilazak mozdaBoljiPut1 = prijedeniPut[mrav].dvaOpt(kapacitetVozila, prijedeniPut[mrav].duljinaObilaska(), najbliziVrhovi); if (mozdaBoljiPut1 != null) { p = 1; prijedeniPut[mrav] = mozdaBoljiPut1; } } } // stutzle-99, 7. str. skroz dolje, local pheromone update: (znaci, put koji mrav izabere gubi dio svojih feromona, // na taj nacin poticemo istrazivanje novih puteva... ukupniPut[mrav] = prijedeniPut[mrav].duljinaObilaska(); double ksi = 0.1; double tau0 = 0.001; int prosli = 1; for (int i = 1; i < prijedeniPut[mrav].put.Count(); i++) { feromoni[prijedeniPut[mrav].put[i].oznaka, prosli] = (1 - ksi) * (feromoni[prijedeniPut[mrav].put[i].oznaka, prosli]) + ksi * tau0; feromoni[prosli, prijedeniPut[mrav].put[i].oznaka] = (1 - ksi) * (feromoni[prosli, prijedeniPut[mrav].put[i].oznaka]) + ksi * tau0; prosli = prijedeniPut[mrav].put[i].oznaka; } } // iteracija je zavrsila i trazimo najbolje rjesenje iz te iteracije, a zatim ga usporedujemo s najboljim koje do sada znamo double ukupniPutIteracijskiMin = ukupniPut[0]; int indeksMin = 0; for (int i = 1; i < brojMrava; i++) { if (ukupniPut[i] < ukupniPutIteracijskiMin) { ukupniPutIteracijskiMin = ukupniPut[i]; indeksMin = i; } } if (ukupniPutIteracijskiMin < globalnaMinDuljina) { boljeRjesenjePrijeKoliko = 0; globalniNajboljiPut = new Obilazak(); foreach (var vrh in prijedeniPut[indeksMin].put) { globalniNajboljiPut.dodajVrh(vrh); } globalnaMinDuljina = ukupniPutIteracijskiMin; } // azuriramo feromone samo po najboljem do sada poznatom rjesenju, za iteracijsko bi islo ovako nekako: // double feromonskiDelta = 1 / ukupniPutIteracijskiMin; // ... plus morale bi se promijeniti jos neke stvari malo nize // u nekom od onih PDF-ova pise da je najbolje prvo azurirati najbolji iteracijski put a onda postepeno sve cesce samo globalni // najbolji put... a inace je, ako biramo samo jedno od tog dvoje, bolje stalno azurirati samo globalni. za sada stoji tako, // radi jednostavnosti. double feromonskiDelta = (1 / globalnaMinDuljina) * parametarEvaporacije; for (int i = 1; i <= brojVrhova; i++) { for (int j = 1; j <= brojVrhova; j++) { feromoni[i, j] *= 1 - parametarEvaporacije; feromoni[j, i] *= 1 - parametarEvaporacije; } } int prosli2 = 1; for (int i = 1; i < globalniNajboljiPut.put.Count(); i++) { feromoni[globalniNajboljiPut.put[i].oznaka, prosli2] += feromonskiDelta; feromoni[prosli2, globalniNajboljiPut.put[i].oznaka] += feromonskiDelta; } boljeRjesenjePrijeKoliko++; if (kolikoIteracija < 0 && boljeRjesenjePrijeKoliko == Math.Abs(kolikoIteracija)) break ; if (kolikoIteracija != 0) brojIteracije++; int napredak = (int)((((double)brojIteracije-1)/(double)kolikoIteracija) * 100); backgroundWorker1.ReportProgress(napredak); } Obilazak o = globalniNajboljiPut; System.Threading.Thread.Sleep(1000); o.nacrtaj("nacrtaj"); string optimalnoDatoteka = fileName.Substring(0, fileName.Length - 4) + ".opt"; System.IO.StreamReader file = new System.IO.StreamReader(optimalnoDatoteka); Obilazak optimalniObilazak = new Obilazak(); optimalniObilazak.dodajVrh(vrhovi[1]); string redak = file.ReadLine(); while (true) { string[] rijeciURedku = redak.Split(' '); int kolikoRijeci = rijeciURedku.Count(); for (int i = 2; i < kolikoRijeci; ++i) { if (rijeciURedku[i] == "") continue; optimalniObilazak.dodajVrh(vrhovi[Convert.ToInt32(rijeciURedku[i]) + 1]); } optimalniObilazak.dodajVrh(vrhovi[1]); redak = file.ReadLine(); if (redak.Split(' ').ElementAt(0) == "cost") break; } file.Close(); optimalniObilazak.nacrtaj("optimalni"); System.Threading.Thread.Sleep(1000); pictureBox1.ImageLocation = @"C:\Users\b\Documents\Visual Studio 2010\Projects\CVRPsaSuceljem\CVRPsaSuceljem\bin\Debug\nacrtaj.png"; pictureBox2.ImageLocation = @"C:\Users\b\Documents\Visual Studio 2010\Projects\CVRPsaSuceljem\CVRPsaSuceljem\bin\Debug\optimalni.png"; label9.Text = "Duljina puta: "; label10.Text = globalniNajboljiPut.duljinaObilaska().ToString(); label12.Text = "Duljina puta: "; label11.Text = optimalniObilazak.duljinaObilaska().ToString(); }
/********************************************************************************************************************** * glavna funkcija, trazi najkrace rjesenje. * vraca najbolje rjesenje koje je nasla. * brojMrava je broj mrava koji u svakoj iteraciji idu konstruirati rjesenje. * alfa i beta su parametri iz formule pomocu koje odredjujemo vjerojatnost odabira pojednog vrha kao iduceg; * sto manji alfa, to veci efekt udaljenosti gradova. sto manji beta, to veci efekt feromonski tragova; * parametarEvaporacije je izmedju 0 i 1. vrijednost 1 bi znacila da feromoni potpuno isparavaju nakon svake iteracije. * kolikoIteracija je broj iteracija koje ce funkcija izvrsiti. ako je postavljen na nulu, broj iteracija je beskonacno; * ako je postavljen na negativan broj, program se vrti sve dok -kolikoIteracija iteracija ne nadjemo bolje rjesenje. *************************************************************************************************************************/ static Obilazak nadjiRjesenje(int brojMrava = 25, double alfa = 1, double beta = 2, double parametarEvaporacije = 0.2, long kolikoIteracija = -100) { // ucitavanje testnih podataka TestniPodaci podaci = new TestniPodaci(@"test\B-n34-k5.vrp"); int brojVrhova = podaci.brojVrhova; Vrh[] vrhovi = new Vrh[brojVrhova + 1]; double kapacitetVozila = podaci.kapacitetVozila; for (int i = 0; i <= brojVrhova; ++i) { vrhovi[i] = podaci.vrhovi[i]; } double gama = 2; double lambda = 2; double[,] feromoni = new double[brojVrhova + 1, brojVrhova + 1]; double[,] eta = new double[brojVrhova + 1, brojVrhova + 1]; //dodani parametri mi i ka, objasnjeno u dokumentu "improvedVRP" double[,] mi = new double[brojVrhova + 1, brojVrhova + 1]; double[,] ka = new double[brojVrhova + 1, brojVrhova + 1]; double miMin = 0.2; double miMax = 0.9; /* svakom vrhu pridruzujemo listu 15 najblizih vrhova i spremamo te podatke u rjecnik * najbliziVrhovi. * za sad iskoristeno samo kod trazenja mogucih vrhova, vidi popunjavanje liste moguciVrhovi */ Dictionary <Vrh, List <Vrh> > najbliziVrhovi = new Dictionary <Vrh, List <Vrh> >(); foreach (var vrh in vrhovi) { najbliziVrhovi[vrh] = vrh.vratiNajblize(vrhovi.ToList(), 15); } for (int i = 0; i <= brojVrhova; i++) { for (int j = 0; j <= brojVrhova; j++) { feromoni[i, j] = 0.001; feromoni[j, i] = 0.001; eta[i, j] = 1 / (vrhovi[i].udaljenost(vrhovi[j], 0)); mi[i, j] = (vrhovi[i].udaljenost(vrhovi[1]) + vrhovi[1].udaljenost(vrhovi[j]) - vrhovi[i].udaljenost(vrhovi[j])) / 100; if (mi[i, j] < miMin) { mi[i, j] = miMin; } if (mi[i, j] > miMax) { mi[i, j] = miMax; } ka[i, j] = (vrhovi[i].potraznja + vrhovi[j].potraznja) / kapacitetVozila; } } Random random = new Random(); int brojIteracije = 1; int boljeRjesenjePrijeKoliko = 0; Obilazak globalniNajboljiPut = new Obilazak(); double globalnaMinDuljina = 1000000; // svaki prolazak kroz sljedecu petlju predstavlja jednu iteraciju algoritma while (brojIteracije - 1 < kolikoIteracija || kolikoIteracija < 0) { double[] ukupniPut = new double[brojMrava]; Obilazak[] prijedeniPut = new Obilazak[brojMrava]; // svaki prolazak kroz sljedecu petlju izvrsava posao jednog mrava for (int mrav = 0; mrav < brojMrava; ++mrav) { List <Vrh> neposjeceniVrhovi = new List <Vrh>(); prijedeniPut[mrav] = new Obilazak(); int brojPotpunoSlucajnih = 0; foreach (var vrh in vrhovi) { if (vrh.oznaka != 0) { neposjeceniVrhovi.Add(vrh); } } neposjeceniVrhovi.Remove(vrhovi[1]); prijedeniPut[mrav].dodajVrh(vrhovi[1]); // vrhovi[1] = skladiste, odatle pocinje svaki obilazak // slijedi konstrukcija kompletnog rjesenja... (to svaki mrav radi) velikaPetlja : while (neposjeceniVrhovi.Count() > 0) { int brojPocetnogVrha = random.Next(0, neposjeceniVrhovi.Count()); Vrh pocetniVrh = neposjeceniVrhovi[brojPocetnogVrha]; Vrh prosliVrh = pocetniVrh; double preostaliKapacitet = kapacitetVozila - prosliVrh.potraznja; prijedeniPut[mrav].dodajVrh(pocetniVrh); neposjeceniVrhovi.Remove(pocetniVrh); while (true) { List <Vrh> moguciVrhovi = new List <Vrh>(); foreach (var vrh in neposjeceniVrhovi) { if (preostaliKapacitet - vrh.potraznja >= 0 && vrh.oznaka != prosliVrh.oznaka && najbliziVrhovi[prosliVrh].Contains(vrh)) { moguciVrhovi.Add(vrh); } } if (moguciVrhovi.Count() == 0) { foreach (var vrh in neposjeceniVrhovi) { if (preostaliKapacitet - vrh.potraznja >= 0 && vrh.oznaka != prosliVrh.oznaka) { moguciVrhovi.Add(vrh); } } } double qParametar = random.NextDouble(); double q = 0.6; double q2 = 1 / ((brojPotpunoSlucajnih * 5) / brojVrhova + 6); // ako mrav vise ne moze prijeci ni u jedan vrh a da ne dostavi vise nego sto ima, krece opet iz skladista if (moguciVrhovi.Count() == 0) { prijedeniPut[mrav].dodajVrh(vrhovi[1]); goto velikaPetlja; } ; Vrh sljedeciVrh = null; // imamo dva moguca nacina za izbor sljedeceg vrha, tj. po dvije razlicite formule // (vidi ANT COLONY SYSTEM, npr. u stuzle-99) i slucajno biramo na koji cemo od ta dva nacina // (za to sluze qParametar i q) // UPDATE: dodan treci nacin: potpuno slucajan izbor! (za potrebe toga dodan parametar q2) if (qParametar > q) { double r = random.NextDouble(); double[] vjerojatnosti = new double[brojVrhova + 1]; double[] rasponOd = new double[brojVrhova + 1]; double[] rasponDo = new double[brojVrhova + 1]; double suma = 0; foreach (var vrh in moguciVrhovi) { suma += (Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta)) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda); } foreach (var vrh in moguciVrhovi) { vjerojatnosti[vrh.oznaka] = (Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta)) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda) / suma; } double rasponDoProsli = 0; foreach (var vrh in moguciVrhovi) { rasponOd[vrh.oznaka] = rasponDoProsli; rasponDo[vrh.oznaka] = rasponOd[vrh.oznaka] + vjerojatnosti[vrh.oznaka]; rasponDoProsli = rasponDo[vrh.oznaka]; } double randomDouble = random.NextDouble(); foreach (var vrh in moguciVrhovi) { if (randomDouble >= rasponOd[vrh.oznaka] && randomDouble < rasponDo[vrh.oznaka]) { sljedeciVrh = vrh; break; } } } else if (qParametar > q2) { double najvecaVrijednost = 0; foreach (var vrh in moguciVrhovi) { double vrijednostZaOvajVrh = Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda); if (vrijednostZaOvajVrh >= najvecaVrijednost) { najvecaVrijednost = vrijednostZaOvajVrh; sljedeciVrh = vrh; } } } else { brojPotpunoSlucajnih++; int indeksSljedeceg = random.Next(0, moguciVrhovi.Count()); sljedeciVrh = moguciVrhovi[indeksSljedeceg]; } preostaliKapacitet -= sljedeciVrh.potraznja; prijedeniPut[mrav].dodajVrh(sljedeciVrh); neposjeceniVrhovi.Remove(sljedeciVrh); } } // mrav je konstruirao neko rjesenje, slijedi dosta agresivno i vremenski skupo lokalno pretrazivanje // stavljeno je da se vrsi samo u svakoj desetoj iteraciji jer bi inace bilo jos puno sporije // ako se zakomentira taj dio koda, ide puno brze, ali i rezultati su losiji // EDIT: dodano da program pamti do sada poznata poboljsanja i onda ne pretrazuje ako smo za neki put vec prije vrsili // dvaOpt ili triOpt... medutim, cini se da to ne ubrzava program (mozda nesto nije dobro napravljeno?)... ali ga ni ne // usporava... znaci, to sad nije toliko bitno, prouciti kasnije. if (brojIteracije % 10 == 9) { Obilazak ulazniObilazak = new Obilazak(); foreach (var vrh in prijedeniPut[mrav].put) { ulazniObilazak.dodajVrh(vrh); } int p = 1; while (p == 1) { p = 0; Obilazak mozdaBoljiPut1 = prijedeniPut[mrav].dvaOpt(kapacitetVozila, prijedeniPut[mrav].duljinaObilaska(), najbliziVrhovi); if (mozdaBoljiPut1 != null) { p = 1; prijedeniPut[mrav] = mozdaBoljiPut1; } } } // stutzle-99, 7. str. skroz dolje, local pheromone update: (znaci, put koji mrav izabere gubi dio svojih feromona, // na taj nacin poticemo istrazivanje novih puteva... ukupniPut[mrav] = prijedeniPut[mrav].duljinaObilaska(); double ksi = 0.2; double tau0 = 0.001; int prosli = 1; for (int i = 1; i < prijedeniPut[mrav].put.Count(); i++) { feromoni[prijedeniPut[mrav].put[i].oznaka, prosli] = (1 - ksi) * (feromoni[prijedeniPut[mrav].put[i].oznaka, prosli]) + ksi * tau0; feromoni[prosli, prijedeniPut[mrav].put[i].oznaka] = (1 - ksi) * (feromoni[prosli, prijedeniPut[mrav].put[i].oznaka]) + ksi * tau0; prosli = prijedeniPut[mrav].put[i].oznaka; } } // iteracija je zavrsila i trazimo najbolje rjesenje iz te iteracije, a zatim ga usporedujemo s najboljim koje do sada znamo double ukupniPutIteracijskiMin = ukupniPut[0]; int indeksMin = 0; for (int i = 1; i < brojMrava; i++) { if (ukupniPut[i] < ukupniPutIteracijskiMin) { ukupniPutIteracijskiMin = ukupniPut[i]; indeksMin = i; } } if (ukupniPutIteracijskiMin < globalnaMinDuljina) { boljeRjesenjePrijeKoliko = 0; globalniNajboljiPut = new Obilazak(); foreach (var vrh in prijedeniPut[indeksMin].put) { globalniNajboljiPut.dodajVrh(vrh); } globalnaMinDuljina = ukupniPutIteracijskiMin; } // azuriramo feromone samo po najboljem do sada poznatom rjesenju, za iteracijsko bi islo ovako nekako: // double feromonskiDelta = 1 / ukupniPutIteracijskiMin; // ... plus morale bi se promijeniti jos neke stvari malo nize // u nekom od onih PDF-ova pise da je najbolje prvo azurirati najbolji iteracijski put a onda postepeno sve cesce samo globalni // najbolji put... a inace je, ako biramo samo jedno od tog dvoje, bolje stalno azurirati samo globalni. za sada stoji tako, // radi jednostavnosti. double feromonskiDelta = (1 / globalnaMinDuljina) * parametarEvaporacije; for (int i = 1; i <= brojVrhova; i++) { for (int j = 1; j <= brojVrhova; j++) { feromoni[i, j] *= 1 - parametarEvaporacije; feromoni[j, i] *= 1 - parametarEvaporacije; } } int prosli2 = 1; for (int i = 1; i < globalniNajboljiPut.put.Count(); i++) { feromoni[globalniNajboljiPut.put[i].oznaka, prosli2] += feromonskiDelta; feromoni[prosli2, globalniNajboljiPut.put[i].oznaka] += feromonskiDelta; } boljeRjesenjePrijeKoliko++; if (kolikoIteracija < 0 && boljeRjesenjePrijeKoliko == Math.Abs(kolikoIteracija)) { return(globalniNajboljiPut); } if (kolikoIteracija != 0) { brojIteracije++; } } return(globalniNajboljiPut); }
public TestniPodaci(string nazivDatoteke) { this.nazivDatoteke = nazivDatoteke; using (var mappedFile1 = MemoryMappedFile.CreateFromFile(nazivDatoteke)) { using (Stream mmStream = mappedFile1.CreateViewStream()) { using (StreamReader sr = new StreamReader(mmStream, Encoding.Default)) { sr.ReadLine(); sr.ReadLine(); sr.ReadLine(); var line = sr.ReadLine(); var lineWords = line.Split(' '); brojVrhova = Convert.ToInt32(lineWords[2]); sr.ReadLine(); line = sr.ReadLine(); lineWords = line.Split(' '); if (lineWords[0] == "CAPACITY") { kapacitetVozila = Convert.ToInt32(lineWords[2]); } vrhovi = new Vrh[brojVrhova + 1]; for (int i = 0; i < brojVrhova + 1; i++) { vrhovi[i] = new Vrh(); } while (true) { line = sr.ReadLine(); lineWords = line.Split(' '); if (lineWords[0] == "NODE_COORD_SECTION") { for (int i = 1; i <= brojVrhova; i++) { var line2 = sr.ReadLine(); var lineWords2 = line2.Split(' '); vrhovi[i].x = Convert.ToInt32(lineWords2[2]); vrhovi[i].y = Convert.ToInt32(lineWords2[3]); } } if (lineWords[0] == "DEMAND_SECTION") { for (int i = 1; i <= brojVrhova; i++) { line = sr.ReadLine(); lineWords = line.Split(' '); vrhovi[i].potraznja = Convert.ToInt32(lineWords[1]); vrhovi[i].oznaka = i; } vrhovi[0].potraznja = 0; vrhovi[0].oznaka = 0; } if (lineWords[0] == "EOF") { break; } } } } } }
/********************************************************************************************************************** * glavna funkcija, trazi najkrace rjesenje. * vraca najbolje rjesenje koje je nasla. * brojMrava je broj mrava koji u svakoj iteraciji idu konstruirati rjesenje. * alfa i beta su parametri iz formule pomocu koje odredjujemo vjerojatnost odabira pojednog vrha kao iduceg; * sto manji alfa, to veci efekt udaljenosti gradova. sto manji beta, to veci efekt feromonski tragova; * parametarEvaporacije je izmedju 0 i 1. vrijednost 1 bi znacila da feromoni potpuno isparavaju nakon svake iteracije. * kolikoIteracija je broj iteracija koje ce funkcija izvrsiti. ako je postavljen na nulu, broj iteracija je beskonacno; * ako je postavljen na negativan broj, program se vrti sve dok -kolikoIteracija iteracija ne nadjemo bolje rjesenje. *************************************************************************************************************************/ static Obilazak nadjiRjesenje(int brojMrava = 25, double alfa = 1, double beta = 2, double parametarEvaporacije = 0.2, long kolikoIteracija = -100) { // ucitavanje testnih podataka TestniPodaci podaci = new TestniPodaci(@"test\B-n34-k5.vrp"); int brojVrhova = podaci.brojVrhova; Vrh[] vrhovi = new Vrh[brojVrhova+1]; double kapacitetVozila = podaci.kapacitetVozila; for (int i = 0; i <= brojVrhova; ++i) { vrhovi[i] = podaci.vrhovi[i]; } double gama = 2; double lambda = 2; double[,] feromoni = new double[brojVrhova + 1, brojVrhova + 1]; double[,] eta = new double[brojVrhova + 1, brojVrhova + 1]; //dodani parametri mi i ka, objasnjeno u dokumentu "improvedVRP" double[,] mi = new double[brojVrhova + 1, brojVrhova + 1]; double[,] ka = new double[brojVrhova + 1, brojVrhova + 1]; double miMin = 0.2; double miMax = 0.9; /* svakom vrhu pridruzujemo listu 15 najblizih vrhova i spremamo te podatke u rjecnik * najbliziVrhovi. * za sad iskoristeno samo kod trazenja mogucih vrhova, vidi popunjavanje liste moguciVrhovi */ Dictionary<Vrh, List<Vrh>> najbliziVrhovi = new Dictionary<Vrh, List<Vrh>>(); foreach (var vrh in vrhovi) { najbliziVrhovi[vrh] = vrh.vratiNajblize(vrhovi.ToList(), 15); } for (int i = 0; i <= brojVrhova; i++) { for (int j = 0; j <= brojVrhova; j++) { feromoni[i, j] = 0.001; feromoni[j, i] = 0.001; eta[i, j] = 1 / (vrhovi[i].udaljenost(vrhovi[j], 0)); mi[i, j] = (vrhovi[i].udaljenost(vrhovi[1]) + vrhovi[1].udaljenost(vrhovi[j]) - vrhovi[i].udaljenost(vrhovi[j]))/100; if(mi[i,j] < miMin) mi[i,j] = miMin; if(mi[i,j] > miMax) mi[i,j] = miMax; ka[i, j] = (vrhovi[i].potraznja + vrhovi[j].potraznja) / kapacitetVozila; } } Random random = new Random(); int brojIteracije = 1; int boljeRjesenjePrijeKoliko = 0; Obilazak globalniNajboljiPut = new Obilazak(); double globalnaMinDuljina = 1000000; // svaki prolazak kroz sljedecu petlju predstavlja jednu iteraciju algoritma while (brojIteracije-1 < kolikoIteracija || kolikoIteracija < 0) { double[] ukupniPut = new double[brojMrava]; Obilazak[] prijedeniPut = new Obilazak[brojMrava]; // svaki prolazak kroz sljedecu petlju izvrsava posao jednog mrava for (int mrav = 0; mrav < brojMrava; ++mrav) { List<Vrh> neposjeceniVrhovi = new List<Vrh>(); prijedeniPut[mrav] = new Obilazak(); int brojPotpunoSlucajnih = 0; foreach (var vrh in vrhovi) { if (vrh.oznaka != 0) neposjeceniVrhovi.Add(vrh); } neposjeceniVrhovi.Remove(vrhovi[1]); prijedeniPut[mrav].dodajVrh(vrhovi[1]); // vrhovi[1] = skladiste, odatle pocinje svaki obilazak // slijedi konstrukcija kompletnog rjesenja... (to svaki mrav radi) velikaPetlja: while (neposjeceniVrhovi.Count() > 0) { int brojPocetnogVrha = random.Next(0, neposjeceniVrhovi.Count()); Vrh pocetniVrh = neposjeceniVrhovi[brojPocetnogVrha]; Vrh prosliVrh = pocetniVrh; double preostaliKapacitet = kapacitetVozila - prosliVrh.potraznja; prijedeniPut[mrav].dodajVrh(pocetniVrh); neposjeceniVrhovi.Remove(pocetniVrh); while (true) { List<Vrh> moguciVrhovi = new List<Vrh>(); foreach (var vrh in neposjeceniVrhovi) { if (preostaliKapacitet - vrh.potraznja >= 0 && vrh.oznaka != prosliVrh.oznaka && najbliziVrhovi[prosliVrh].Contains(vrh)) moguciVrhovi.Add(vrh); } if (moguciVrhovi.Count() == 0) { foreach (var vrh in neposjeceniVrhovi) { if (preostaliKapacitet - vrh.potraznja >= 0 && vrh.oznaka != prosliVrh.oznaka) moguciVrhovi.Add(vrh); } } double qParametar = random.NextDouble(); double q = 0.6; double q2 = 1 / ( ( brojPotpunoSlucajnih * 5 )/brojVrhova + 6); // ako mrav vise ne moze prijeci ni u jedan vrh a da ne dostavi vise nego sto ima, krece opet iz skladista if (moguciVrhovi.Count() == 0) { prijedeniPut[mrav].dodajVrh(vrhovi[1]); goto velikaPetlja; }; Vrh sljedeciVrh = null; // imamo dva moguca nacina za izbor sljedeceg vrha, tj. po dvije razlicite formule // (vidi ANT COLONY SYSTEM, npr. u stuzle-99) i slucajno biramo na koji cemo od ta dva nacina // (za to sluze qParametar i q) // UPDATE: dodan treci nacin: potpuno slucajan izbor! (za potrebe toga dodan parametar q2) if (qParametar > q) { double r = random.NextDouble(); double[] vjerojatnosti = new double[brojVrhova + 1]; double[] rasponOd = new double[brojVrhova + 1]; double[] rasponDo = new double[brojVrhova + 1]; double suma = 0; foreach (var vrh in moguciVrhovi) { suma += (Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta)) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda); } foreach (var vrh in moguciVrhovi) { vjerojatnosti[vrh.oznaka] = (Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta)) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda) / suma; } double rasponDoProsli = 0; foreach (var vrh in moguciVrhovi) { rasponOd[vrh.oznaka] = rasponDoProsli; rasponDo[vrh.oznaka] = rasponOd[vrh.oznaka] + vjerojatnosti[vrh.oznaka]; rasponDoProsli = rasponDo[vrh.oznaka]; } double randomDouble = random.NextDouble(); foreach (var vrh in moguciVrhovi) { if (randomDouble >= rasponOd[vrh.oznaka] && randomDouble < rasponDo[vrh.oznaka]) { sljedeciVrh = vrh; break; } } } else if (qParametar > q2) { double najvecaVrijednost = 0; foreach (var vrh in moguciVrhovi) { double vrijednostZaOvajVrh = Math.Pow(feromoni[prosliVrh.oznaka, vrh.oznaka], alfa) * Math.Pow(eta[prosliVrh.oznaka, vrh.oznaka], beta) * Math.Pow(mi[prosliVrh.oznaka, vrh.oznaka], gama) * Math.Pow(ka[prosliVrh.oznaka, vrh.oznaka], lambda); if (vrijednostZaOvajVrh >= najvecaVrijednost) { najvecaVrijednost = vrijednostZaOvajVrh; sljedeciVrh = vrh; } } } else { brojPotpunoSlucajnih++; int indeksSljedeceg = random.Next(0, moguciVrhovi.Count()); sljedeciVrh = moguciVrhovi[indeksSljedeceg]; } preostaliKapacitet -= sljedeciVrh.potraznja; prijedeniPut[mrav].dodajVrh(sljedeciVrh); neposjeceniVrhovi.Remove(sljedeciVrh); } } // mrav je konstruirao neko rjesenje, slijedi dosta agresivno i vremenski skupo lokalno pretrazivanje // stavljeno je da se vrsi samo u svakoj desetoj iteraciji jer bi inace bilo jos puno sporije // ako se zakomentira taj dio koda, ide puno brze, ali i rezultati su losiji // EDIT: dodano da program pamti do sada poznata poboljsanja i onda ne pretrazuje ako smo za neki put vec prije vrsili // dvaOpt ili triOpt... medutim, cini se da to ne ubrzava program (mozda nesto nije dobro napravljeno?)... ali ga ni ne // usporava... znaci, to sad nije toliko bitno, prouciti kasnije. if (brojIteracije % 10 == 9) { Obilazak ulazniObilazak = new Obilazak(); foreach (var vrh in prijedeniPut[mrav].put) { ulazniObilazak.dodajVrh(vrh); } int p = 1; while (p == 1) { p = 0; Obilazak mozdaBoljiPut1 = prijedeniPut[mrav].dvaOpt(kapacitetVozila, prijedeniPut[mrav].duljinaObilaska(), najbliziVrhovi); if (mozdaBoljiPut1 != null) { p = 1; prijedeniPut[mrav] = mozdaBoljiPut1; } } } // stutzle-99, 7. str. skroz dolje, local pheromone update: (znaci, put koji mrav izabere gubi dio svojih feromona, // na taj nacin poticemo istrazivanje novih puteva... ukupniPut[mrav] = prijedeniPut[mrav].duljinaObilaska(); double ksi = 0.2; double tau0 = 0.001; int prosli = 1; for (int i = 1; i < prijedeniPut[mrav].put.Count(); i++) { feromoni[prijedeniPut[mrav].put[i].oznaka, prosli] = (1 - ksi) * (feromoni[prijedeniPut[mrav].put[i].oznaka, prosli]) + ksi*tau0; feromoni[prosli, prijedeniPut[mrav].put[i].oznaka] = (1 - ksi) * (feromoni[prosli, prijedeniPut[mrav].put[i].oznaka]) + ksi * tau0; prosli = prijedeniPut[mrav].put[i].oznaka; } } // iteracija je zavrsila i trazimo najbolje rjesenje iz te iteracije, a zatim ga usporedujemo s najboljim koje do sada znamo double ukupniPutIteracijskiMin = ukupniPut[0]; int indeksMin = 0; for (int i = 1; i < brojMrava; i++) { if (ukupniPut[i] < ukupniPutIteracijskiMin) { ukupniPutIteracijskiMin = ukupniPut[i]; indeksMin = i; } } if (ukupniPutIteracijskiMin < globalnaMinDuljina) { boljeRjesenjePrijeKoliko = 0; globalniNajboljiPut = new Obilazak(); foreach (var vrh in prijedeniPut[indeksMin].put) { globalniNajboljiPut.dodajVrh(vrh); } globalnaMinDuljina = ukupniPutIteracijskiMin; } // azuriramo feromone samo po najboljem do sada poznatom rjesenju, za iteracijsko bi islo ovako nekako: // double feromonskiDelta = 1 / ukupniPutIteracijskiMin; // ... plus morale bi se promijeniti jos neke stvari malo nize // u nekom od onih PDF-ova pise da je najbolje prvo azurirati najbolji iteracijski put a onda postepeno sve cesce samo globalni // najbolji put... a inace je, ako biramo samo jedno od tog dvoje, bolje stalno azurirati samo globalni. za sada stoji tako, // radi jednostavnosti. double feromonskiDelta = (1 / globalnaMinDuljina) * parametarEvaporacije ; for (int i = 1; i <= brojVrhova; i++) { for (int j = 1; j <= brojVrhova; j++) { feromoni[i, j] *= 1 - parametarEvaporacije; feromoni[j, i] *= 1 - parametarEvaporacije; } } int prosli2 = 1; for (int i = 1; i < globalniNajboljiPut.put.Count(); i++) { feromoni[globalniNajboljiPut.put[i].oznaka, prosli2] += feromonskiDelta; feromoni[prosli2, globalniNajboljiPut.put[i].oznaka] += feromonskiDelta; } boljeRjesenjePrijeKoliko++; if (kolikoIteracija < 0 && boljeRjesenjePrijeKoliko == Math.Abs(kolikoIteracija)) return globalniNajboljiPut; if (kolikoIteracija != 0) brojIteracije++; } return globalniNajboljiPut; }
public Obilazak optimalniObilazak(string instanca, string optimalnoRjesenje) { TestniPodaci podaci = new TestniPodaci(instanca); int brojVrhova = podaci.brojVrhova; Vrh[] vrhovi = new Vrh[brojVrhova + 1]; double kapacitetVozila = podaci.kapacitetVozila; for (int i = 0; i <= brojVrhova; ++i) { vrhovi[i] = podaci.vrhovi[i]; } System.IO.StreamReader file = new System.IO.StreamReader(optimalnoRjesenje); Obilazak optimalniObilazak = new Obilazak(); optimalniObilazak.dodajVrh(vrhovi[1]); string redak = file.ReadLine(); while (redak != null) { string[] rijeciURedku = redak.Split(' '); int kolikoRijeci = rijeciURedku.Count(); if (kolikoRijeci < 3) continue; for (int i = 2; i < kolikoRijeci; ++i) { optimalniObilazak.dodajVrh(vrhovi[Convert.ToInt32(rijeciURedku[i]) + 1]); } optimalniObilazak.dodajVrh(vrhovi[1]); } file.Close(); return optimalniObilazak; }