Processing math: 100%

sábado, 7 de fevereiro de 2015

Estudos de otimização: Programação linear

Um dos tópicos de revisão de otimização que estou estudando é o de Programação Linear. Neste tipo de problema de otimização tanto a função objetivo quanto as restrições são funções lineares das variáveis.

Apesar de problemas deste tipo poderem ser expressos de diversas maneiras, a maneira abaixo, chamada de forma canônica, é frequentemente utilizada por materiais da área (e também por pacotes computacionais).

{minimizef(x)=cTx s.a.Ax=bx0

Conversão para a forma canônica

Seguem abaixo alguns ajustes que podem ser feitos para converter um problema linear qualquer para sua forma canônica:

  1. Se problema for maximizecTx, converta-o para minimizecTx;
  2. Se houverem restrições de ou , adicione uma variável si para cada restrição e converta-as em restrições de igualdade da seguinte maneira: se Aixb, então Aixsi=b; se Aixb, então Aix+si=b.
  3. Se houverem variáveis que podem ser negativas (sem restrição xi0), faça a substituição xi=x(+)ix()i,x(+)i0,x()i0.

As transformações (1) e (3) são relativamente fáceis de serem entendidas. No caso da transformação (2), é possível atribuir a si qualquer valor (pois s_i não faz parte da função objetivo). Desta maneira, se durante o proceso de otimização Aix=b, então basta que si=0.

O problema Dual

Todo problema de minimização (primal) em PL possui um problema de maximização (dual) relacionado de modo que ambos possuem o mesmo valor de função objetivo e é trivial encontrar a resposta de um a partir do outro.

Dado o problema linear na forma canônica acima, o problema dual consiste em:

{maximizef(y)=bTy s.a.ATyc

A partir da solução ótima y do problema dual, é possível obter a solução ótima x do problema primal.

Seja u=cATy. O vetor u contem a "distância" que cada restrição dual está da igualdade. Uma restrição somente restringe a função objetivo se está "saturada", ou seja, ui=0. Desta maneira, podemos ignorar toda coluna de A tal que uj0, pois esta restrição não está ativa na solução do dual.Se o problem dual tiver solução única o número de componentes não zero de u será igual ao número de linhas de A (número de restrições primais). Logo, podemos obter x resolvendo o seguinte sistema linear:

A[:,ui=0]x=b.

As posições de x^* tal que ui0 são completadas com xi=0, pois todas as restrições de A já são satisfeitas com igualdade pelas outras componentes de xi.

Exercício exemplo

Como exemplo, resolveremos um exercício simples de programação linear que ilustra os conceitos revistos.

Enunciado:

Uma startup busca fabricar máquinas de lavar falantes ao menor custo possível. Existem três maneiras de produzí-las: manualmente, semi automaticamente e automaticamente. A produção manual de uma máquina de lavar requer 1 minuto de trabalho qualificado, 40 minutos de trabalho não qualificado e 3 minutos de montagem. Para a produção semi automáticas os valores são, respectivamente, 4, 30 e 2 minutos e para a produção automática os valores são 8, 20 e 4 minutos. A startup dispões de 4500 minutos de trabalho qualificado, 36000 de trabalho não qualificado e 2700 minutos de montagem. Os custos de produção manual, semi automático e automático são, respectivamente, 70, 80 e 85 euros.

1. Formule o problema como um PL e dê uma solução.

No problema acima, as variáveis são o número x1,x2ex3 de máquinas feitas por cada tipo de produção (manual, semi-automática e automática). O custo a ser minimizado é, portanto 70x1+80x2+85x3. Existem restrições quanto ao número de máquinas a serem produzidas e à capacidade de produção da fábrica (número de minutos para cada tipo de trabalho).

Abaixo o problema completo:

{minimizef(x)=70x1+80x2+85x3 s.a.x1+x2+x3=999x1+4x2+8x3450040x1+30x2+20x3360003x1+2x2+4x32700x0

Solução usando scipy:

import numpy as np
from scipy.optimize import linprog
from numpy.linalg import solve

A_eq = np.array([[1,1,1]])
b_eq = np.array([999])

A_ub = np.array([
[1, 4, 8],
[40,30,20],
[3,2,4]])

b_ub = np.array([4500, 36000,2700])

c = np.array([70, 80, 85])

res = linprog(c, A_eq=A_eq, b_eq=b_eq, A_ub=A_ub, b_ub=b_ub,
bounds=(0, None))
print('Valor otimo:', res.values()[3], '\nX:', res.values()[4])
Valor otimo: 73725.0
X: [ 636.  330.   33.]

2. Converta o problema para sua forma canônica

Para converter o problema acima para a forma canônica é necessário adicionar variáveis de folga para as restrições de desigualdade. Desta forma, o problema se torna:

{minimizef(x)=70x1+80x2+85x3 s.a.x1+x2+x3=999x1+4x2+8x3+s1=450040x1+30x2+20x3+s2=360003x1+2x2+4x3+s3=2700x0,s0

Solução usando scipy:

A = np.array([
[1, 1, 1, 0, 0, 0],
[1, 4, 8, 1, 0, 0],
[40, 30, 20, 0, 1, 0],
[3, 2, 4, 0, 0, 1]])

b = np.array([999, 4500, 36000, 2700])
c = np.array([70, 80, 85, 0, 0, 0])

res = linprog(c, A_eq=A, b_eq=b, bounds=(0, None))
print('Valor otimo:', res.values()[3], '\nX:', res.values()[4])
Valor otimo: 73725.0
X: [  636.   330.    33.  2280.     0.     0.]

Note que s1=2280 implica que x1+4x2+8x34500.

3. Apresente o dual do problema e mostre que ambas formulações obtem os mesmos resultados.

O problema dual correspondente é dado por:

{maximizef(y)=999y1+4500y2+360000y3+2700y4 s.a.y1+y2+40y3+3y470y1+4y2+30y3+2y480y1+8y2+20y3+4y485y20y30y40

Solução usando scipy: Para a solução usando scipy podemos utilizar as variáveis declaradas anteriormente. Note que como linprog resolve problemas de minimização, o sinal da função objetivo está trocado.

res = linprog(-b, A_ub=A.T, b_ub=c, bounds=[(None,None), (None,None),
(None,None), (None,None)])
y = res.values()[4]
print('Valor otimo:', -res.values()[3], '\nY:', y)

u = c - A.T.dot(y)
Ar = A[:, np.abs(u)< 1e-10]
x_1 = solve(Ar, b)
print(x_1)
x = np.array([0.0] * len(c))
x[np.abs(u)< 1e-10] = x_1
x[np.abs(u)> 1e-10] = 0
print('Solucao X a partir de Y:', x)
Valor otimo: 73725.0
Y: [ 108.33333333    0.           -0.83333333   -1.66666667]
[  636.   330.    33.  2280.]
Solucao X a partir de Y: [  636.   330.    33.  2280.     0.     0.]

Nenhum comentário:

Postar um comentário