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=bx≥0
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:
- Se problema for maximizecTx, converta-o para minimize−cTx;
- 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 Aix≥b, então Aix−si=b; se Aix≤b, então Aix+si=b.
- Se houverem variáveis que podem ser negativas (sem restrição xi≥0), faça a substituição xi=x(+)i−x(−)i,x(+)i≥0,x(−)i≥0.
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.ATy≤c
A partir da solução ótima y∗ do problema dual, é possível obter a solução ótima x∗ do problema primal.
Seja u=c−ATy. 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 uj≠0, 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 ui≠0 são completadas com x∗i=0, pois todas as restrições de A já são satisfeitas com igualdade pelas outras componentes de x∗i.
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+8x3≤450040x1+30x2+20x3≤360003x1+2x2+4x3≤2700x≥0
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=2700x≥0,s≥0
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+8x3≤4500.
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+3y4≤70y1+4y2+30y3+2y4≤80y1+8y2+20y3+4y4≤85y2≤0y3≤0y4≤0
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