7 Apéndice

Anuncio
7 Apéndice
7.1 Apéndice A: Implementación de la Heurística de
Takahashi y Matsuyama
En este apartado se encuentra el código que implementa la heurística de
Takahashi y Matsuyama en código C.
7.1.1 Función Principal
Esta función recibe el nombre del fichero que contiene el enunciado del
problema (por ejemplo steind5.txt) y va llamando al resto de funciones
auxiliares, para procesar el fichero, reservar memoria, ejecutar el algoritmo,
imprimir los resultados, y liberar la memoria.
En caso de fallo de reserva de memoria o apertura del descriptor de
fichero lo indica con un mensaje enviado a la salida estándar (pantalla).
La función ejecuta el algoritmo de Takahashi Matsuyama implementado
en la función Soladmisible por cada nodo terminal. Ya se comentó
anteriormente, que la solución depende del nodo terminal por el cual se
comience a buscar una solución.
/********************************************************************/
/* NOMBRE: Función Principal
*/
/* Proyecto Fin de Carrera
*/
/* Asunto: Resolución del Problema de Steiner
*/
/********************************************************************/
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include "prototipo.h"
# include "constantes.h"
Capítulo 7–70
int main(int argc,char **argv)
{
FILE *fich_grafo=NULL;
/* Descriptor de fichero */
float **grafo=NULL;
/* Grafo enunciado
float ***subgrafo=NULL;
/* Grafo Sol Takahashi en
*/
[0] y en [1] valor flujo */
int **lista_steiners=(int**)malloc(sizeof(int*));
int **lista_terminales=(int**)malloc(sizeof(int*));
int *num_nodos=malloc(sizeof(int));
int *num_terminales=malloc(sizeof(int));
int nodoorigen;
int iteracion;
int i,j;
float resul=0;
if (argc==2)
{
fich_grafo=fopen(argv[1],"r");
if (fich_grafo!=NULL)
{
grafo=(float **)lee_archivo(fich_grafo,num_nodos,
num_terminales,lista_steiners,lista_terminales);
float *dist=(float*)malloc(*num_nodos * sizeof(float));
int *pred=(int*)malloc(*num_nodos * sizeof(int));
/* Para cada nodo terminal como origen */
for(i=0;i<(*num_terminales);i++)
{
nodoorigen=(*lista_terminales)[i];
subgrafo=(float ***)soladmisible(grafo,*num_nodos,
lista_terminales,*num_terminales,nodoorigen,pred,dist);
Capítulo 7–71
}
liberamem(grafo,lista_steiners,lista_terminales,num_nodos,
num_terminales,subgrafo,pred,dist);
}
else
{
fprintf(stderr,"error en archivo\n");
fclose(fich_grafo);
}
}
else
{
fprintf(stderr, "error en argumentos del programa\n");
}
system("PAUSE");
return(0);
}
7.1.2 Funciones Auxiliares
En este apartado se describen las funciones llamadas desde la función
principal. Entre ellas se encuentran:
•
Lee_archivo. Procesa el fichero de entrada que contiene el
enunciado, obteniendo la tabla que describe el grafo con sus
costes, el número de nodos total, terminales y la lista de nodos
Steiners y terminales.
•
Liberamem. Liberar la memoria reservada para almacenar las
listas, valores y matrices que se pasan por referencia a lo largo
del código.
•
Soladmisible.
Implementa
la
heurística
del
algoritmo
de
Takahashi y Matsuyama propiamente. Imprimiendo por pantalla
Capítulo 7–72
los resultados obtenidos
partiendo desde todos los nodos
terminales.
•
Rutamin. Calcula la distancia entre un nodo dado (origen) y el
resto de los nodos del grafo que se le pasa como parámetro de
entrada.
/********************************************************************/
/* NOMBRE: Funciones auxiliares
*/
/* Proyecto Fin de Carrera
*/
/* Asunto: Resolución del Problema de Steiner
*/
/********************************************************************/
# include <stdio.h>
# include <stdlib.h>
# include <ctype.h>
# include <string.h>
# include <math.h>
# include "constantes.h"
# include "prototipo.h"
/********************************************************************/
/* Procesa el fichero de entrada que contiene el enunciado,
*/
/* obteniendo la tabla que describe el grafo con sus costes, el
*/
/* número de nodos total, terminales y la lista de nodos
*/
/* Steiners y terminales.Lee el fichero de entrada y lo traduce a
*/
/* una matriz que representa el grafo
*/
/********************************************************************/
float **lee_archivo(FILE *fichero,int *num_nodos, int *num_terminales,
int **lista_steiners, int **lista_terminales)
{
char linea[MAXLINEA];
char aux[TAMNODOS];
float **matriz=NULL;
int i=0;
int j=0;
int k=0;
int encontrado=0;
Capítulo 7–73
int num_arcos;
int arco;
int nodoorigen;
int nododestino;
int peso;
fgets(linea,MAXLINEA,fichero);
while (!isspace((int)linea[i+1]))
/* Leo nº nodos */
{
aux[i]=linea[i+1];
i++;
}
aux[i]='\0';
*num_nodos=atoll(aux);
/*devuelvo por ref el tamaño del grafo*/
while (isspace((int)linea[i+1]))
{
i++;
}
while (!isspace((int)linea[i+1]))
/* Leo nº arcos */
{
aux[j]=linea[i+1];
i++;
j++;
}
aux[j]='\0';
num_arcos=atoll(aux);
/* Reserva matriz num_nodos*num_nodos */
matriz=(float**)malloc((*num_nodos)* sizeof(float *));
for (i=0;i<(*num_nodos);i++)
{
matriz[i]=(float*)malloc((*num_nodos)* sizeof(float));
}
/* Inicializando matriz que representa el grafo*/
for (i=0;i<*num_nodos;i++)
{
for (j=0;j<(*num_nodos);j++)
Capítulo 7–74
{
matriz[i][j]=MAXLONG;
}
}
arco=0;
while(arco<num_arcos)
/* Rellena la matriz con los pesos de los
arcos*/
{
fgets(linea,MAXLINEA,fichero);
i=0;
j=0;
while (!isspace((int)linea[i+1]))
/* Leo nodo origen */
{
aux[i]=linea[i+1];
i++;
}
aux[i]='\0';
nodoorigen=atoll(aux);
while (isspace((int)linea[i+1]))
/* paso los espacios */
{
i++;
}
j=0;
while (!isspace((int)linea[i+1]))
/* Leo nodo destino */
{
aux[j]=linea[i+1];
i++;
j++;
}
aux[j]='\0';
nododestino=atoll(aux);
while (isspace((int)linea[i+1]))
/*paso los espacios*/
{
i++;
}
Capítulo 7–75
j=0;
while (!isspace((int)linea[i+1]))
/* Leo el peso */
{
aux[j]=linea[i+1];
i++;
j++;
}
aux[j]='\0';
peso=atoll(aux);
/* Relleno la matriz que representa el grafo quitando al indice 1
para que empiece en 0 */
matriz[nodoorigen-1][nododestino-1]=matriz[nododestino1][nodoorigen-1]=peso;
arco++;
}
fgets(linea,MAXLINEA,fichero);
/* Leo nº nodos terminales */
i=0;
while (!isspace((int)linea[i+1]))
{
aux[i]=linea[i+1];
i++;
}
aux[i]='\0';
*num_terminales=atoll(aux);
*lista_terminales=(int*)malloc((*num_terminales)* sizeof(int));
fgets(linea,MAXLINEA,fichero);
/* Almaceno los terminales*/
i=0;
for(k=0;k< *num_terminales;k++)
{
j=0;
while (!isspace((int)linea[i+1]))
/* Tomo un número */
{
aux[j]=linea[i+1];
i++;
j++;
}
Capítulo 7–76
aux[j]='\0';
(*lista_terminales)[k]=atoll(aux)-1;
if (isspace((int)linea[i+1]))
/* Paso los espacios */
{
i++;
}
if (linea[i+1]=='\n')
{
fgets(linea,MAXLINEA,fichero);
i=0;
}
}
*lista_steiners=(int*)malloc(((*num_nodos)-(*num_terminales))*
sizeof(int)); /*los que no son terminales son steiners*/
j=0;
for(k=0;k<*num_nodos;k++)
{
encontrado=0;
for(i=0;i<*num_terminales;i++)
{
if(((*lista_terminales)[i])==k)
{
encontrado=1;
}
}
if(!encontrado)
{
(*lista_steiners)[j]=k;
j++;
}
}
return(matriz);
}
/********************************************************************/
/* Dado una matriz que representa al grafo y su dimension,
*/
/* proporciona el arbol minimo que une un nodo origen con el resto, */
Capítulo 7–77
/* y los predecesores que guian hasta la raiz del árbol (origen)
*/
/********************************************************************/
void rutamin(float **grafo, int num_nodos,int origen,float *dist,int
*pred)
{
int i=0;
int j=0;
int bucleneg;
int actualizo;
int iteraciones=0;
int aux;
/* para recorrer los predecesores en busca de bucles negativos */
float suma;
float MAX;
MAX=MAXLONG;
for(i=0;i<num_nodos;i++)
{
dist[i]=MAX;
min,
los
/*para comenzar la búsqueda de dist
inicializo
al
máximo
para
que
vayan
actualizándose */
pred[i]=MAX
/* Por poner un valor para detectar
que aun no existe predecesor */
}
dist[origen]=0;
pred[origen]=origen;
do
{
actualizo=0;
for(i=0;i<num_nodos;i++)
{
for(j=0;j<num_nodos;j++)
{
suma=dist[i]+grafo[i][j];
if(
(dist[j] > suma) && (grafo[i][j]!=MAX) &&(pred[i]!=MAX))
/* si es de un nodo a el mismo (caso i,j con i=j) no entra */
{
Capítulo 7–78
/* Compruebo que no paso por un predecesor => bucle negativo */
aux=i;
bucleneg=0;
/*
printf("origen=%d
dist
a
%d=%f,
grafo%d-%d
=
%f\n",origen,j,
dist[j],i,j,grafo[i][j]);*/
do
{
/* printf("j=%d aux=%d pred[%d]=%d\n",j,aux,aux,pred[aux]);*/
if(j==aux)
{
/* existe bucle negativo, no actualizo, obvio ese arco */
bucleneg=1;
/* printf("detectado bucle neg %d %d\n",aux,origen);*/
}
else
{
if((pred[aux]!=MAX)&&(pred[aux]!=aux))
/*
si
existe
el
pred y no es el mismo (para el caso origen)*/
{
aux=pred[aux];
/*printf("new aux=%d\n",aux) ;*/
}
}
}
while((aux!=origen)&&(!bucleneg)&&(pred[aux]!=MAX));
/* como es un árbol debería llegar al origen si no existe bucle (A
MENOS QUE SEA UN PESO NEGATIVO)*/
/* printf("DIst %d,%d=%f\n",i,j,dist[j]); */
if((bucleneg!=1)&&(j!=origen))
/* ultima cond por si esta el bucle con el nodo origen*/
{
dist[j]=(dist[i]+grafo[i][j]);
pred[j]=i;
actualizo=1;
/* printf("conectado %d\ta %d\tcon %f\n",i,j,dist[j]);*/
}
}
}
}
/* iteraciones++;printf("vez %d\n",iteraciones);*/
}
Capítulo 7–79
while(actualizo==1);
/* Para imprimir las distancias del árbol, NO las mínimas de la sol
admisible */
/*
for (i=0;i<num_nodos;i++)
{
printf("dist a %d=%f\n",i,dist[i]);
}
printf("%d Iteraciones\n",iteraciones);*/
}
/********************************************************************/
/* Esta función implementa el algoritmo de Takahashi y Matsuyama
*/
/* grafo es una tabla que contiene los pesos de los arcos, que
*/
/* indica cómo es el grafo.
*/
/********************************************************************/
float ***soladmisible(float **grafo,int num_nodos,
int **lista_terminales,int num_terminales,int nodoorigen,int
*pred,float *dist)
{
int k;
/* Desde que nodo pisado parto a buscar la ruta min */
int i;
/* Hasta que terminal estoy llegando */
int l;
/* Recorro lista terminales para comprobar que no estuviera
ya en la lista de terminales pisados */
int j;
/* Recorre para añadir, la lista de nodos pisados */
int m; /* Para copiar la lista de los predecesores del terminal
cercano encontrado */
float mindist=MAXLONG;
int terminalcercano;
int num_steiners=num_nodos-num_terminales;
int *desdepred=(int*)malloc(num_nodos * sizeof(int));
float ***subgrafo=(float***)malloc(num_nodos * sizeof(float **));
/* 0 es la sol adminisible y 1 los flujos */
int *nodos=(int*)malloc(num_nodos * sizeof(int));
/* lista de nodos que pertenecen al subgrafo */
int *terminales=(int*)malloc(num_terminales * sizeof(int));
int sig;
int ant;
Capítulo 7–80
int encontrado;
int termpisados;
int desdeorigen;
for (i=0;i<num_nodos;i++)
{
subgrafo[i]=(float**)malloc((num_nodos)* sizeof(float*));
for(j=0;j<num_nodos;j++)
{
subgrafo[i][j]=(float*)malloc(2 * sizeof(float));
}
}
/*Inicializar submatriz y nodos */
for (i=0;i<num_nodos;i++)
{
nodos[i]=-1;
for (j=0;j<num_nodos;j++)
{
subgrafo[i][j][0]=MAXLONG; /* Peso del arco */
subgrafo[i][j][1]=0;
/* Flujo árbol
*/
}
}
/*Inicializar lista terminales encontrados */
for (i=0;i<(num_terminales);i++)
{
terminales[i]=-1;
}
nodos[0]=terminales[0]=nodoorigen; /*inicialmente dispongo del nodo
origen elegido en la lista de terminales y nodos pisados */
termpisados=1;
/* He añadido el origen que es terminal.Resto 1 xq
queda un terminal menos */
j=1;
/* Avanzo la lista de nodos pisados al añadir el
origen */
do
/* printf("Iteracion\n"); */
{
mindist=MAXLONG;
Capítulo 7–81
/* busco desde los
for(k=0;nodos[k]!=-1;k++)
nodos que ya tengo de subgrafo el terminal mas cercano*/
{
rutamin(grafo,num_nodos,nodos[k],dist,pred);
/* printf("Rutamin %d\n",nodos[k]);*/
/* hacia los terminales */
for(i=0;i<num_terminales;i++)
{
encontrado=0;
/*printf("de %d a %d dist %f \n",nodos[k],dist[i]);*/
for(l=0;l<num_terminales;l++)
/* que no estuviera ya ese terminal */
{
if(terminales[l]==(*lista_terminales)[i])
{
encontrado=1;
}
}
if((dist[(*lista_terminales)[i]]<mindist)&&(!encontrado))
{
mindist=dist[(*lista_terminales)[i]];
terminalcercano=(*lista_terminales)[i];
desdeorigen=nodos[k];
/* Guardo desde que nodo del subgrafo nuevo diponible he encontrado el
terminal más cercano */
for(m=0;m<num_nodos;m++)
/* Guardo los predecesores desde ese origen, al terminal mas cercano
encontrado */
{
desdepred[m]=pred[m];
}
}
/* printf("dist=%f, nodo=%d\n",mindist,terminalcercano);*/
}
}
/*printf("
mas
cercano
a
%d
=
%d
con
peso
%f\n",desdeorigen,
terminalcercano,mindist); */
sig=terminalcercano;
/* He encontrado un terminal cercano a
Capítulo 7–82
lo que ya tenia distinto de los que había */
terminales[termpisados]=sig;/* Añado el terminal que he encontrado
a la lista */
/* Una vez encontrado, guardo su ruta
while(sig!=desdeorigen)
en nodos y grafo */
/* printf("sig=%d desdeorigen=%d\n",sig,desdeorigen); */
{
encontrado=0;
for(i=0;i<num_nodos;i++)
/* No añadir uno que ya existía en la lista de nodos y arcos pisados
*/
{
if(nodos[i]==sig)
{
encontrado=1;
}
}
if(!encontrado)
{
nodos[j]=sig;
/*printf("meto %d\n",sig);*/
ant=sig;
j++;
sig=desdepred[ant];
subgrafo[ant][sig][0]=grafo[ant][sig];
/* no guardo el simetrico para que el grafo sea dirigido y poder sacar
el flujo (subgrafo[sig][ant]= */
}
}
/*printf("%d\n",termpisados); /* en realidad +1 porque empieza desde 0
y comprueba hasta < */
termpisados++;
}
while(termpisados<num_terminales);
/* Obtener flujos */
Capítulo 7–83
/* Para cada terminal recorre
for(i=0;i<num_terminales;i++)
el camino al origen y añade su flujo */
/* printf("terminal %dº=%d\n",i,terminales[i]); */
{
if(terminales[i]!=nodoorigen)
{
sig=terminales[i];
do
{
for(j=0;j<num_nodos;j++)
{
if(subgrafo[sig][j][0]!=MAXLONG)
{
subgrafo[sig][j][1]=subgrafo[sig][j][1]+1;
/* Añado una unidad de flujo */
/* printf("arco %d-%d flujo %f\n",sig,j,subgrafo[sig][j][1]);*/
sig=j;
}
}
/* printf("sig=%d
nodoori=%d\n",sig,nodoorigen); */
}
while(sig!=nodoorigen);
}
}
/* Imprime de la sol admisible de Takahashi
printf("Matriz del árbol resultante y flujo sol admisible\n"); */
float resultado=0;
for (i=0;i<num_nodos;i++)
{
for (j=0;j<num_nodos;j++)
{
/*if(subgrafo[i][j][0]!=MAXLONG)printf("[%d][%d]=%f",i,j,
subgrafo[i][j][0]);
if(subgrafo[i][j][1]!=0) printf("flujo %f
\n",subgrafo[i][j][1]);*/
if (subgrafo[i][j][0]!=MAXLONG)
{
resultado=resultado+subgrafo[i][j][0];
}
}
}
Capítulo 7–84
printf("\nOrigen
%d
Resultado
Sol.
admisible
Takahashi
%f\n",nodoorigen,resultado);
free (terminales);
free (nodos);
free (desdepred);
return(subgrafo);
}
/********************************************************************/
/* Esta función libera todos los rangos de memoria reservados para
*/
/* la ejecución del programa. Requiere conocer el número de nodos
*/
/* y número de nodos terminales para poder liberar las tablas y
*/
/* listas apuntadas por la etiqueta correspondiente.
*/
/********************************************************************/
void liberamem(float **grafo, int **lista_steiners,
int **lista_terminales,int *num_nodos,int *num_terminales,
float ***subgrafo,int *pred, float *dist)
{
int i;
int j;
for (i=0;i<*num_nodos;i++)
{
free(grafo[i]);
for (j=0;j<*num_nodos;j++)
{
free(subgrafo[i][j]);
}
free(subgrafo[i]);
}
free(grafo);
free(subgrafo);
free(num_nodos);
free(*lista_steiners);
free(lista_steiners);
Capítulo 7–85
free(num_terminales);
free(*lista_terminales);
free(lista_terminales);
free(dist);
free(pred);
}
7.1.3 Prototipo de Funciones
A continuación de describen los prototipos de las funciones empleadas.
/********************************************************************/
/* NOMBRE: Prototipo de funciones
*/
/* Proyecto Fin de Carrera
*/
/* Asunto: Resolución del Problema de Steiner
*/
/********************************************************************/
float **lee_archivo(FILE *fichero,int *num_nodos, int *num_terminales,
int **lista_steiners, int **lista_terminales);
void liberamem(float **grafo, int **lista_steiners,
int **lista_terminales,
int *num_nodos,int *num_terminales,float ***subgrafo,int *pred,
float *dist);
float ***soladmisible(float **grafo,int num_nodos,
int **lista_terminales,int num_terminales, int nodoorigen,
int *pred,float *dist);
void rutamin(float **grafo, int num_nodos,int origen,float *dist,
int *pred);
7.1.4 Constantes
En este apartado se describen las constantes empleadas.
#
define
MAXLINEA
600
/*
El
fichero
de
entrada
lista
todos
los
steiners en una línea */
Capítulo 7–86
# define MAXLONG 9999
# define TAMNODOS 4
/* Acota el peso de los arcos */
/* Num de digitos maximo de un nodo */
Capítulo 7–87
7.2 Anexo B: Implementación en Matlab
7.2.1 Lectura del Fichero de Entrada y Solución Inicial.
La siguiente función procesa dos ficheros de texto que han de pasarse
como parámetros de entrada a la función:
•
Fichero con el enunciado del problema (por ejemplo steinb.txt) con el
formato indicado en la Tabla 4 Formato del Fichero de Entrada.
•
Fichero exportado del algoritmo de Takahashi y Matsuyama con los
valores de los flujos que circulan por cada arco del grafo para la
solución calculada.
Como parámetros de salida ofrece el número de nodos total, el número de
nodos terminales, una tabla con el grafo y sus costes, la lista de terminales,
y la solución inicial proporcionada por la heurística de Takahashi y
Matsuyama en el vector x0.
function[num_nodo,num_terminales,grafo,lista_terminales,x0]=LoadInf2(s,s2)
fid=fopen(s,'r');
if fid==-1
disp('Error, the file could not be opened')
else
line=fgets(fid);
index=findstr(line,' ');
num_nodo=str2num(line(index(1)+1:index(2)-1));
grafo=zeros(num_nodo^2,1);
num_arc=str2num(line(index(2)+1:(length(line))));
for i=1:num_arc
line=fgets(fid);
Capítulo 7–88
index=findstr(line,' ');
nodo_origen=str2num(line(index(1)+1:index(2)-1));
nodo_destino=str2num(line(index(2)+1:index(3)-1));
peso=str2num(line(index(3)+1:(length(line))));
grafo((nodo_origen-1)*num_nodo+nodo_destino,1)=peso;
grafo((nodo_destino-1)*num_nodo+nodo_origen,1)=peso;
%grafo
end
line=fgets(fid);
index=findstr(line,' ');
num_terminales=str2num(line);
lista_terminales=zeros(1,num_terminales);
leidos=0;
while (leidos~=num_terminales)
line=fgets(fid);
index=findstr(line,' ');
items=length(index); %items mas 1, por el espacio que hay al ppio de
linea
for(h=1:items-1)
if(h~=items-1)
lista_terminales(h)=str2num(line(index(h)+1:index(h+1)-1));
else
lista_terminales(h)=str2num(line(index(h)+1:(length(line))));
end
end
leidos=leidos+items-1;
end
if (fclose(fid)==-1)
disp('Error, the file could not be closed')
end
end
% Leer segundo fichero de flujos exportado del codigo C
Capítulo 7–89
fid=fopen(s2,'r');
if fid==-1
disp('Error, the file could not be opened')
else
tam=num_nodo^2;
x0=zeros(1,tam);
for i=1:tam
index=findstr(line,'.');
x0(i)=str2num(line(1:(index(1)-1)));
end
if (fclose(fid)==-1)
disp('Error, the file could not be closed')
end
end
x0=transpose(x0);
7.2.2 Código Restricción Lineal.
Las dos líneas siguientes, implementan la restricción lineal:
xij ≥ 0, → ∀(i, j ) ∈ A
A=diag(-ones(1,dim));
b=zeros(dim,1);
7.2.3 Código Restricción no Lineal
El siguiente código implementa la restricción no lineal:
1 → si j ∈ T

X ( j , N ) − X ( N , j ) = 0 → si j ∉ T , j ≠ e, ∀j ∈ N

− | T | +1 → si j = e
Los códigos que implementan la parte derecha e izquierda de la restricción se
encuentran separados para mayor claridad:
Capítulo 7–90
•
Implementación del lado izquierdo (Aeq):
function Aeq= CreaAeq(num_nodo);
dim_sq=num_nodo;
Aeq=zeros(dim_sq);
for i=1:dim_sq;
for j=1:dim_sq;
for h=1:dim_sq;
if(i==j)
if(j~=h)
Aeq(i,(j-1)*dim_sq+h)=1;
end
end
if (i~=j)
if(h==i)
Aeq(i,(j-1)*dim_sq+h)=-1;
end
end
end
end
end
Aeq=[Aeq zeros(dim_sq,1)];
•
Implementación del lado derecho (beq):
function [restriccion]=conservacion(lista_terminales,nodo_end,num_nodo)
for(nodo=1:num_nodo)
if(nodo==nodo_end)
restriccion(nodo,1)=1-length(lista_terminales);
elseif(findstr(lista_terminales,nodo))
restriccion(nodo,1)=1;
else restriccion(nodo,1)=0;
end
end
Capítulo 7–91
7.2.4
Función Principal.
A continuación se muestra el código de la función principal que resuelve
el problema:
% Resolución del Pb de Optimización.
function [x,fval]= Problema (enunciado,flujos)
[num_nodo,num_terminales,grafo,lista_terminales,x0]=LoadInf2(enunciado,fluj
os);
nodo_end=lista_terminales(1);
dim=length(x0);
A=diag(-ones(1,dim));
b=zeros(dim,1);
Aeq=CreaAeq(num_nodo);
beq=conservacion(lista_terminales,nodo_end,num_nodo);
X=zeros(dim,1);
fval=zeros(1,1);
[X,fval]=fmincon(inline('-sum(-grafo.*[((x-).^2)./((x.^2)+1)])'),x0,A,b,Aeq,beq);
En esta última línea es donde figura la función de integridad, que en
este caso se trata de la primera.
7.3 Apéndice
B:
Implementación
Empleando
las
Condiciones de Kunh Tucker
En este capítulo, se documenta cómo se implementó una heurística
basada en las funciones de integridad y el empleo de las condiciones de Kunh
Tucker.
Capítulo 7–92
7.3.1 Planteamiento del Problema
Tal y como vimos en el apartado 5.3.2, llegamos a que podíamos
transformar la función objetivo a:
max
∑ c · f (x
ij
ij
)
( i , j )∈A
st:
1 → sij ∈ T

X ( j , N ) − X ( N , j ) = 0 → sij ∉ T , j ≠ e, ∀j ∈ N
− | T | +1 → sij = e

xij ≥ 0, → ∀(i, j ) ∈ A
Sin embargo, el modelo así expresado resulta ser no acotado, pues las
restricciones del modelo no obligan a que si xij > 0 entonces xji = 0, o
viceversa. De esta forma, al no evaluarse la función objetivo sobre las anterior
variables binarias sino sobre las variables continuas xij directamente, podría
darse la situación (para la función de integridad nº 1) de que el valor óptimo se
estableciera en xij = ∞ y xij = ∞ -k, lo cual provoca un flujo neto sobre el arco
no dirigido de k unidades en la dirección i→j.
Con el objeto de evitar el efecto descrito, se elige como función objetivo
g(xij)=f((xij- xji)2 ) en lugar de f(xij) con lo cual se evalúa directamente el flujo
neto sobre el arco no dirigido, es decir, las k unidades del caso anterior.
Hay que destacar que el valor de la función “g”, g(xij)=g(xji)= f((xij- xji)2 ),
es el mismo se evalúe sobre el arco dirigido i→j , que sobre el arco dirigido
j→i. Recogiendo el efecto de la variable binaria (que identifica el arco no
dirigdo {i,j}).
Con esto, el modelo queda como sigue:
max Z =
∑c
( i , j )∈ A
ij
· g ( x ij ) =
∑c
ij
· f (( x ij − x ji ) 2 )
( i , j )∈ A
Capítulo 7–93
st:
1 → sij ∈ T

X ( j , N ) − X ( N , j ) = 0 → sij ∉ T , j ≠ e, ∀j ∈ N
− | T | +1 → sij = e

xij ≥ 0, → ∀(i, j ) ∈ A
A partir de ahora, y a diferencia del modelo que se propuso en el
apartado 5.3, se define la función gij(xij)= cij · f((xij- xji)2 ).
Las condiciones de Kunh Tucker quedan de la siguiente forma:
µ i − µ j + vij = −∇g ij ( x ij ) , ∀(i, j ) ∈ A
vij .xij = 0 , ∀(i, j ) ∈ A
1 → si j ∈ T

X ( j , N ) − X ( N , j ) = 0 → si j ∉ T , j ≠ e, ∀j ∈ N

− | T | +1 → si j = e
vij ≥ 0, → ∀(i, j ) ∈ A
xij ≥ 0, → ∀(i, j ) ∈ A
µ i libre
Donde la variable vij actúa como holgura de la restricción. Cuando la
restricción µ i − µ j + vij = −∇g ij ( x ij ) se cumple con signo de igualdad, la
holgura toma el valor 0, circulando flujo sobre el arco (i,j). Esto es, el arco (i,j)
perteneciente al árbol de Steiner. Si la holgura toma valor distinto de cero, no
circula flujo sobre el arco (i,j), y por tanto dicho arco no pertenece al árbol de
Steiner. En resumen, se pueden dar dos situaciones:
Caso 1.1.-
Capítulo 7–94
Si vij >0 y vji >0. Entonces µ i − µ j < −∇g ij ( x ij ) y µ j − µ i < −∇g ji ( x ji ) siendo por
tanto, xij = 0 y xji = 0 respectivamente.
En cuyo caso el arco no dirigido (i-j) no está incluido en el árbol de
Steiner. Esto es, y ij = 0.
2.Caso 2.
Si
vij
=0
o
bien
vji
bien µ j − µ i = −∇g ji ( x ji ) siendo
=0.
por
Entonces
tanto,
xij
>
µ i − µ j = −∇g ij ( x ij )
0
o
bien
xji
o
>
0
respectivamente.
En cuyo caso el arco no dirigido (i-j) sí está incluido en el árbol de
Steiner. Esto es, y ij > 0.
7.3.2 Método Heurístico de la Solución
7.3.2.1
Identificación de una Solución Admisible
Para comenzar a iterar necesitamos una solución admisible inicial. Por
ejemplo tomada del algoritmo de Takahashi y Matsuyama que se ha
implementado.
7.3.2.2
Modelo Lineal Asociado a una Solución Admisible
La función gij(xij), definida en el apartado 7.3.1, es claramente no lineal.
Sin embargo, consideramos su aproximación lineal en el entorno de cada una
de las soluciones admisibles del problema (por ejemplo la antes señalada).
En estas condiciones son formulables los problemas primal y dual del
nuevo problema lineal asociado.
Problema Primal:
Capítulo 7–95
max Z =
∑ ∇g
ij
( x ij0 )· x ij ≡ − min
( i , j )∈ A
∑ − ∇g
ij
( x ij0 )·x ij
( i , j )∈ A
st:
1 → si j ∈ T

X ( j , N ) − X ( N , j ) = 0 → si j ∉ T , j ≠ e, ∀j ∈ N

− | T | +1 → si j = e
xij ≥ 0, → ∀(i, j ) ∈ A
Donde N representa el conjunto de nodos del grafo y A el conjunto de
arcos dirigidos (nótese que existirá un arco (i,j) y un arco (j,i) por cada arco
no-dirigido {i,j}.
Problema dual
Se puede escribir de dos formas:
min ∑ µ i
i∈T
st: µ i − µ j ≥ ∇g ij ( xij0 ) , ∀(i, j ) ∈ A
µ i libre
o bien:
− max ∑ µ i
i∈T
st: µ i − µ j ≤ ∇g ij ( x ij0 ) , ∀(i, j ) ∈ A
µ i libre
Donde la variable libre µe se fija arbitrariamente al valor de 0, en base al
rango de la matriz de incidencia del problema de distribución original (primal),
que dispone de una fila redundante.
Capítulo 7–96
0
Cuando todos los valores de − ∇g ij ( xij ) son positivos, y si se obvia el
signo “menos” que precede a la función objetivo y el cual no condiciona el
proceso de optimización, el problema linealizado, en el entorno de la solución
admisible {x0}, puede ser resuelto mediante el algoritmo de la ruta mínima
para grafos con bucles.
Esto es correcto, puesto que el mínimo de la suma es siempre una cota
superior de la suma de los mínimos, de acuerdo con el principio de
optimalidad de Bellman. Esto es:


Min ∑ µ i  ≥ ∑ ( Min( µ i ))
 i∈T  i∈T
Así, si µi0 es la ruta mínima (en términos de distancia mínima
acumulada) desde el nodo e hasta el terminal i, entonces la solución {µ0}
compuesta por todas las rutas mínimas desde cada terminal i ∈T es la solución
óptima del problema dual ya que todos estos valores corresponden a rutas
mínimas admisibles. Y la suma de los mínimos es siempre menor o igual que
el mínimo de la suma.
7.3.2.3
Discusión sobre la Admisibilidad del Problema Lineal
El argumento anterior es siempre válido que el problema primal sea
acotado (y por consiguiente su dual no incompatible). Esto siempre sucede
cuando los costes del problema de la ruta mínima (en nuestro caso
0
− ∇g ij ( xij ) ) sean todos positivos.
Cuando existen costes positivos y negativos, el problema de la ruta
mínima no pude incluir ningún bucle en el que la suma total de la distancia sea
negativa. En tal caso el problema primal sería no acotado y el dual
Capítulo 7–97
incompatible. Así por ejemplo, cualquier grafo que incluya como subgrafo
alguno como el descrito en la Figura 20 Bucle Negativo, constituye un
problema no acotado para el cálculo de la ruta mínima. Nótese que siempre se
0
0
verifica que cij = − ∇g ij ( xij ) = − ∇g ji ( x ji ) = cji.
Figura 20 Bucle Negativo
Las restricciones asociadas al problema dual para el arco {1,2} de la
Figura 20 Bucle Negativo, serían las siguientes:
µ1 - µ2 ≥ +2
µ2 - µ1 ≥ +2
Resultando que es incompatible. Por ello hay que analizar los valores de
0
para los que − ∇g ij ( xij ) pueda ser negativo. Esto sólo sucede para valores de
Xij>1, donde la pendiente de las funciones de integridad es positiva y por
0
tanto el valor de − ∇g ij ( xij ) negativo.
0
Los valores de Xij ≤ 1 no ofrecen problemas ya que − ∇g ij ( xij ) ≥ 0
(tomando el valor de 0 en el caso de Xij=1).
Así pues, los arcos que pueden generar subgrafos no válidos son
aquellos sobre los que circula flujo positivo mayor que 1.
Capítulo 7–98
Hay que garantizar que ningún bucle que incluya dichos arcos sume un
valor total neto negativo. En otro caso se produce un ciclo de realimentación
negativo y el problema (primal) es no-acotado. Para construir un grafo sobre el
cual el problema linealizado tenga solución acotada y para los casos en que se
verifique la propiedad anterior (bucle de coste total negativo) se procede a
eliminar aquel arco del bucle que transporte una cantidad de flujo menor
positiva (idealmente un arco que transporte una unidad de flujo, en caso de
que exista empate, se seleccionará aleatoriamente).
Al conjunto de bucles de coste total negativo le denominaremos B, y se
dice que un bucle b es de coste total neto negativo si b ∈ B.
Así, se define el grafo G*, de la forma G*(N,A\E).
Tomadas estas consideraciones para la no-formación de bucles de coste
total negativo, y una vez procedido a ala generación del nuevo grafo G*, el
problema es resoluble aplicando el siguiente procedimiento iterativo:
1. Se parte de una solución admisible del primal {x0}.
0
2. Se evalúan los valores de − ∇g ij ( xij ) , para todo (i,j) ∈ A tal que
Xij0 ∈(x0). Se construye el grafo G*.
3. Se resuelve el problema linealizado haciendo uso del algoritmo
de la ruta mínima para grafos con bucles. En el libro de D. Cieslik
se encuentran varias formas de conseguirlo.
4. La solución obtenida en el paso 3, {µ0, vij0}, implica una nueva
solución admisible en flujo, xN, a partir de las condiciones de
optimalidad de Kunh-Tucker.
5. Se vuelve al paso 2.
Capítulo 7–99
El algoritmo de la ruta mínima para grafos con bucles proporcionala
solución óptima (mínima distancia y camino) desde el nodo e hasta todos los
nodos del gafo (en este caso también el conjunto de nodos terminales) . Esta
solución
determina
los
valores
{µ0}
del
problema
dual
y
los
arcos
pertenecientes a la ruta mínima. La determinación de la solución del problema
primal, {x0}, se establece contabilizando una unidad de flujo por cada arco
situado en cada una de las rutas mínimas que parten de cada terminal, i. Con
ello se dispone de un nuevo patrón de flujo {x0}, y se puede volver a aplicar el
procedimiento iterativo.
La solución así calculada, incluye, siempre, un conjunto de arcos que
son variables básicas no-degeneradas (x=1), y que determinan un camino
entre el nodo e y el nodo seleccionado como destino final, así como otro
conjunto
de
arcos
que
son
variables
básicas-degeneradas
(x=0).
La
consideración de todas las variables básicas (ya sean degeneradas o no)
determina un árbol.
7.3.2.4
Selección del nodo e
Si bien en el modelo lineal de programación mixta, la selección del nodo
e no afectaba a la calidad de la solución final, la selección del nodo e en el
modelo no lineal sí afecta a la solución de dicho problema. Es decir, el nuevo
problema que se genera al asociar un nodo e a nodo terminal concreto de la
red, son todos distintos, ya que los flujos Xij lo son. En efecto, la elección del
nodo afecta tanto al cálculo del árbol resultado de la aplicación del algoritmo
de la ruta mínima, como a la determinación de los flujos.
Capítulo 7–100
Es por ello que hay que considerar todos los posibles candidatos a ser
nodo e, y comprobar aquella selección que provee el mejor valor del problema
de Steiner. El conjunto de candidatos lo definen todos los nodos terminales del
grafo. De esta forma se habrá de ejecutar |T| veces el algoritmo de cálculo de
la ruta mínima para grafos con bucles.
7.3.2.5
Pseudocódigo
Para implementarlo, se programó un código que realiza lo siguiente:
1. Se lee del fichero de entrada el enunciado del problema, para
crear la matriz que contiene el grafo y los pesos, y la lista de
nodos terminales y Steiners.
2. Se elige un nodo terminal como e.
3. Se calcula el gradiente de la función de integridad evaluado en el
punto de la solución calculada {x0}.
4. Como en la función que se creó para calcular la ruta mínima, ya
se integró la detección de bucles negativos (simplemente, se
descartaba ese arco), no era necesario detectarlos y eliminarlos.
Resolver
el
problema
linealizado
mediante
aplicación
del
algoritmo de la ruta mínima en grafos con bucles, tomando como
pesos, el “menos” el gradiente de la función.
5. Determinar la nueva solución Xadm que proporcionan las
condiciones de Kunh Tucker asociadas a la solución. Si Vij=0,
entonces el arco {i,j} pertenece al árbol de Steiner y sus flujos se
calculan por evaluación directa.
6. Volver al paso 3 mientras converja la solución y queden nodos
terminales por considerar como e.
Capítulo 7–101
Capítulo 7–102
Descargar