Programação Linear
Motivação
No quotidiano de uma empresa, os gestores são confrontados com opções a tomar: dadas certas restrições e limitações quanto a recursos disponíveis, precisam de escolher rumos que maximizem o lucro da mesma - produzir mais circuitos de um tipo do que outro por serem mais rentáveis, por exemplo.
Da mesma forma, um partido político pode ter de escolher entre "secções estratégicas" a incluir no seu programa eleitoral - cada uma destas secções pode agradar a uma parte do eleitorado e ser pior aceite por outra parte, tendo então de haver uma escolha baseada nos efeitos esperados da inclusão de cada secção no programa.
A programação linear procura desconstruir e chegar a uma resposta concreta para estes problemas, maximizando o ganho consoante um dado conjunto de restrições.
O exemplo dos partidos políticos referido acima é bastante fácil de explicar com a ajuda de uma tabela. Tenhamos os seguintes efeitos da inclusão de certas políticas num programa eleitoral:
Urbano | Suburbano | Rural | |
---|---|---|---|
Construção de Estradas | |||
Legalização das Drogas Leves | |||
Subsídios Agrícolas | |||
Impostos no Combustível |
Aqui, cada coluna corresponde a um círculo eleitoral, enquanto que cada linha corresponde a uma política. Cada círculo eleitoral tem, claro, número diferente de eleitores: respetivamente cem mil, duzentos mil e cinquenta mil eleitores recenseados. Cada entrada na matriz corresponde aos ganhos (em milhares de eleitores) por cada gastos em publicidades.
Como podemos observar, diferentes círculos eleitorais reagem de forma diferente à mesma medida: subsídios agrícolas, por exemplo, são recebidos com enorme satisfação pela população rural, diretamente beneficiada pelos mesmos, enquanto que os setores urbano e suburbano reagem com indiferença, visto que a medida não os afeta diretamente.
O objetivo passará, então, por tentar obter a maioria absoluta (pelo menos dos votos) minimizando os custos. A resposta, claro, será obtida através da programação linear. Antes de introduzir o conceito em si, tentemos formalizar o problema:
Temos que as quantias a gastar por medida em publicidade são dados, respetivamente, por e . O nosso objetivo passa, então, por minimizar . Visto que queremos procurar satisfazer ao máximo o eleitorado, procuramos também que cada círculo eleitoral tenha, no mínimo, dos votos. Assim sendo, iríamos procurar a solução que minimize os custos sujeita às restrições:
Formulações
Formulação Geral - Programação Linear
Procuramos, ao resolver problemas via programação linear, otimizar uma função linear sujeita a um conjunto de restrições (desigualdades) lineares.
Dados um conjunto de números reais e um conjunto de variáveis , podemos definir uma função linear para essas variáveis tal que:
Uma função linear pode estar sujeita a um dado conjunto de restrições lineares, igualdades e/ou desigualdades lineares (em relação a um qualquer número real).
Temos que qualquer solução que satisfaça o conjunto de restrições de uma dada função é uma solução exequível, e que o conjunto de soluções deste género corresponde à região exequível da função. Por fim, diz-se que uma dada formulação é exequível se tiver pelo menos uma solução exequível (e não exequível caso contrário).
Tenhamos um conjunto de restrições tal que:
Temos que a aplicação destas restrições resulta num gráfico tal que:
Ora, a interseção de todas estas restrições resulta num conjunto convexo como o que se apresenta na figura:
Tenhamos ainda que a função objetivo é .
Visto que temos que a maximizar, vamos considerar aqui várias linhas tal que , onde é um número real. O objetivo será encontrar o maior número tal que a interseção da linha com o conjunto convexo apresentado acima não é vazio - aí, maximizamos o objetivo!
Observemos a imagem abaixo:
Aqui, o valor máximo de tal que a interseção da linha com o conjunto convexo apresentado acima não é vazio é : todas as retas "abaixo" dela têm valores menores, que não maximizam o objetivo, e nenhuma "acima" tem interseção não vazia com o conjunto convexo. Assim sendo, encontrámos a solução ótima: e , com objetivo maximizado .
Importa realçar que as soluções ótimas para este tipo de problemas encontram-se sempre nos vértices do conjunto convexo. Mais ainda, se a interseção da linha que maximiza o objetivo com o conjunto convexo for um segmento de reta, então os vértices desse segmento são ambos soluções ótimas.
O conjunto convexo, a região que andamos a referir tantas vezes, chama-se Simplex - nome este que vamos ouvir bastante durante esta secção.
São estudadas duas formas de especificar programas lineares - as formas Standard e Slack. Diferem quanto à especificação das respetivas restrições: a primeira opta por especificar as restrições como desigualdades, enquanto que a segunda opta por especificar as restrições como igualdades (exceto para os problemas que requerem variáveis necessariamente não negativas). Olhemos para ambas:
Forma Standard
A forma standard procura maximizar , tendo em conta um conjunto de restrições tal que:
Mais ainda, notar que a forma standard requer variáveis com valor não negativo, e que todas as suas desigualdades sejam apresentadas tal que menor-ou-igual-a, não havendo lugar a restrições de igualdade (a importância da orientação do sinal da inequação será claro mais à frente).
Podemos representar o programa de forma mais compacta ainda, recorrendo a uma representação matricial do problema.
Ao criar uma matriz , matriz essa , e recorrendo a três vetores , e , podemos representar o programa tal que procuramos maximizar , tendo em conta as restrições:
Podemos reparar que as representações coincidem com as apresentadas anteriormente.
A conversão de um programa linear para a forma standard pode não ser trivial: podemos enfrentar problemas com resolução não óbvia.
-
Caso o objetivo seja minimizar o objetivo (em vez de procurar a maximização do mesmo), a solução passa por inverter o sinal de todos os coeficientes da função objetivo (mantendo os das restrições), procurando agora a maximização desse novo objetivo. Por exemplo, minimizar uma função objetivo seria o mesmo que maximizar (não havendo quaisquer alterações nos coeficientes das restrições).
-
Caso haja variáveis que possam ser negativas, essas mesmas variáveis são substituídas pela diferença de duas variáveis auxiliares, essas sim com a restrição de serem não negativas. Por exemplo, no programa abaixo:
- Objetivo: Maximizar
- Restrições:
Aqui, não apresenta a restrição requerida. Assim sendo, substituímo-la por , ficando então com o programa linear:
- Objetivo: Maximizar
- Restrições:
-
Caso haja restrições via igualdade, ao invés de desigualdades, estas são substituídas por um par de desigualdades. Ter é equivalente a ter , pelo que podemos pegar no exemplo imediatamente acima e reescrevê-lo tal que:
- Objetivo: Maximizar
- Restrições:
-
Caso haja restrições via desigualdade maior-ou-igual-a, basta apenas trocar os sinais em ambos os lados. Pegando no programa acima, ficaríamos com:
- Objetivo: Maximizar
- Restrições:
De realçar que a restrição que obriga as variáveis a ser não negativas é mantida.
Por fim, as variáveis com apóstrofes acabariam por ser renomeadas, ficando então com:
- Objetivo: Maximizar
- Restrições:
Forma Slack
O tratamento que estamos a fazer ao programa não é despropositado - é fulcral para o algoritmo Simplex, abordado mais abaixo, resolver o problema de forma eficiente. O algoritmo, contudo, prefere ainda uma forma diferente de expressar o programa: todas as restrições (exceto as das variáveis serem não negativas) devem ser expressadas sob a forma de igualdade. Para tal, recorremos a , uma variável de slack que representa a diferença entre ambos os lados da nova igualdade - a inequação passou a equação, logo algo teve necessariamente de mudar para deixar de ser uma inequação.
Por exemplo, tendo a restrição
convertê-la-íamos para a forma Slack tal que:
Na forma slack, optamos por escrever as novas igualdades sob a notação , ao invés de . O exemplo utilizado na forma standard ficaria então:
- Objetivo: Maximizar
- Restrições:
Podemos reparar num pormenor interessante: estamos a procurar sempre escrever os em função de outras variáveis - neste caso em função das "variáveis iniciais", as da função que pretendemos maximizar. Dizemos que estas variáveis auxiliares (as do lado esquerdo das equações, portanto) são as variáveis básicas, que dependem das outras, e que as restantes são as variáveis não-básicas. Mais ainda, tal como no exemplo do gráfico que foi utilizado acima onde denotámos , temos que na forma slack a função objetivo está definida como
e que portanto o programa acima na forma slack seria escrito tal que:
Formalizando por fim, a forma slack pode ser então descrita tal que:
-
corresponde ao conjunto de índices das variáveis não-básicas;
-
corresponde ao conjunto de índices das variáveis básicas;
-
, com e ;
Na função objetivo, corresponde a uma constante, cuja utilidade será aparente mais à frente.
Exemplo - Representação Matricial
Tenhamos o seguinte programa na forma slack:
Ora, temos aqui que (as variáveis não-básicas) e (as variáveis básicas).
Voltando a pegar na representação matricial abordada inicialmente na forma Standard, podemos então escrever:
Podemos então notar que, de forma sucinta:
- As colunas de correspondem às variáveis não-básicas, ;
- As linhas de correspondem às variáveis básicas, ;
- As entradas de correspondem aos coeficientes de cada variável não básica na equação associada a cada variável básica do conjunto de restrições;
Mais ainda, as linhas da matriz vertical correspondem à variável de slack de cada igualdade do conjunto de restrições associada a cada variável básica. Para além disso, em correspondem aos coeficientes de cada variável não básica na função objetivo.
Algoritmo Simplex
O Algoritmo Simplex corresponde à abordagem clássica para resolver problemas de otimização linear. Tem por base a interpretação geométrica (o conjunto convexo) apresentada no início desta página. Funciona como uma espécie de "eliminação de Gauss-Jordan" para inequações.
A ideia por detrás do algoritmo poderá ser entendida mais facilmente com a ajuda de um exemplo. Tenhamos o seguinte programa linear na forma slack:
Inicialmente, olhamos para a solução básica para o problema: colocamos todas as variáveis não-básicas a zero, ficando com a solução igual a , . Se for exequível, dizemos que o programa tem uma uma solução básica exequível.
O algoritmo procura, a cada iteração, reescrever as equações do programa, de forma a encontrar diferentes soluções para o mesmo. Além disso, temos que as alterações que faremos serão sempre com vista a não decrescer - não tem necessariamente de aumentar, mas nunca irá diminuir entre iterações.
Para reescrever as igualdades, pegamos na variável não-básica do programa cujo coeficiente no objetivo é mais positivo e olhamos para as variáveis básicas, procurando pensar "qual é o máximo que posso aumentar a variável não-básica sem que as variáveis básicas se tornem negativas". Mais ainda, temos que o algoritmo termina quando todas as variáveis não-básicas do objetivo tiverem coeficientes negativos. Temos então de olhar para cada uma das restrições e procurar qual a variável a alterar (e como) em relação ao programa acima, qual é o máximo que podemos aumentar (já que tem o coeficiente mais positivo)?
-
. Igualando todas as variáveis exceto a zero, obtemos uma maximização de para esta restrição: ;
-
- fazemos o mesmo que acima, ficando com ;
-
- fazemos o mesmo que acima, ficando com . Esta é a restrição mais apertada, dando então o máximo que podemos aumentar .
Temos, então, que uma restrição no programa reescrito será (vem da última restrição, a que considerámos apertada, escrita agora com em função das outras variáveis).
Descoberta a restrição mais apertada, trocamos os papéis de e , tanto nas restrições como no objetivo, ficando com um programa tal que:
De realçar que as duas últimas restrições e o objetivo foram obtidos substituindo pelo lado direito da nova igualdade que envolve como variável básica: .
A operação que foi agora realizada, esta troca entre uma variável básica e uma não-básica, é a Operação Pivot. Numa pivotagem, consideramos a variável não-básica a variável de entrada (vai entrar no conjunto das variáveis básicas), e a variável básica a variável de saída (vai sair do conjunto de variáveis básicas). O algoritmo Simplex procura, então, realizar pivotagens sucessivas até não haver mais soluções básicas exequíveis - até todos os coeficientes das variáveis não-básicas na função objetivo serem negativos.
Voltamos então a igualar todas as variáveis não-básicas a zero, ficando com solução igual a e .
De seguida, procuramos reescrever novamente o problema: desta vez, foquemo-nos na variável , a variável não-básica com coeficiente mais positivo no objetivo. Aqui, a terceira restrição (a que tem como variável básica) é a mais apertada, restringindo a . Assim sendo, o programa reescrito será (e tendo em conta agora ):
O objetivo é, então, aumentado para .
Por fim, reescrevemos o programa tendo em conta (a única variável não-básica com coeficiente positivo restante), ficando com:
Chegámos, assim, a um ponto em que todas as variáveis não-básicas na função objetivo têm coeficiente negativo. Assim sendo, podemos dar o algoritmo por terminado, dizendo que a solução ótima para o programa é , com .
Solução Exequível Inicial
A conversão de um programa linear para a forma slack nem sempre é simples. Vejamos o caso abaixo:
- Objetivo: Maximizar ;
- Restrições:
Ao igualar e a , a segunda restrição é desrespeitada (ficamos com ) - não há uma solução exequível inicial. Podemos, contudo, verificar se o programa tem alguma solução exequível, recorrendo a um programa auxiliar.
Seja um programa linear na forma standard, é definido tal que:
- Objetivo: Maximizar ;
- Restrições:
Temos assim que só é exequível caso o valor ótimo para o objetivo de seja (a prova será adicionada num futuro próximo).
Comecemos com o programa linear inicial abaixo:
- Objetivo: Maximizar ;
- Restrições:
Temos que a solução básica não é exequível (ficaríamos com ). Assim sendo, construímos um programa linear auxiliar tal que:
- Objetivo: Maximizar ;
- Restrições:
O programa encontra-se atualmente na forma standard. A sua conversão para a forma slack é trivial:
O primeiro passo ao resolver os programas auxiliares passa sempre por pivotar - o algoritmo estaria concluído caso contrário, já temos o pretendido. Optamos, aqui, por pivotar com a restrição que contiver a constante mais negativa - neste caso, pivotando com a restrição que contém como variável básica, tal que:
Este programa apresenta solução básica exequível! Aplicaríamos agora o algoritmo Simplex normalmente até obter uma solução para este programa auxiliar tal que : caso tal solução exista, o programa original é exequível, e partiremos da solução aqui encontrada quando voltarmos ao programa original. A solução para o programa auxiliar seria (depois de alguns passos):
Teríamos, então, uma solução tal que .
Para resolver o problema inicial, substituiríamos o objetivo pelo original (substituindo no objetivo pela restrição acima), e mantendo o conjunto de restrições praticamente intacto, com exceção da remoção de qualquer referência a nas mesmas. Procuraríamos, então, resolver:
A partir daqui, resolveríamos este programa linear com a ajuda do algoritmo Simplex.
Teorema Fundamental da Programação Linear
Pegando num qualquer programa linear na forma standard, este tem três possibilidades (só podendo pertencer a uma delas):
-
Tem uma solução ótima com valor objetivo finito, valor esse que pode ser obtido num dos vértices da respetiva região exequível;
-
É não exequível;
-
É unbounded, não limitado: o valor objetivo pode ser arbitrariamente maximizado.
Abaixo encontra-se o gráfico correspondente a um programa linear unbounded. Podemos notar que há infinitas retas que maximizam o valor objetivo, cada uma com arbitrariamente maior que a anterior, e todas elas cuja interseção com a região exequível é não vazia:
Como nota final, resta afirmar que caso após iterações o algoritmo ainda não tenha terminado, podemos admitir que este está em ciclo. Há, ao todo, variáveis e formas de escolher , pelo que há formas de slack únicas - mais que isso e o algoritmo está em ciclo. São raros, mas existem, e para os eliminar recorre-se à Regra de Bland: em caso de empate na escolha de variáveis, escolhe-se sempre a variável com menor índice.
Dualidade
Em programação linear, a dualidade é uma propriedade associada à própria análise de um problema: podemos exprimir um programa de duas maneiras diferentes, com soluções equivalentes, com a diferença principal entre elas a ser o objetivo pretendido: maximizar ou minimizar um dado objetivo. Vamos, nesta secção, finalmente aproveitar a representação matricial de um programa linear introduzida mais acima.
Pensemos então num exemplo concreto. Uma empresa procura maximizar o lucro obtido ao vender dois produtos, e , sendo que:
- vende uma unidade do produto por euro e uma unidade do produto por euros (consideremos que produzir o produto não tem qualquer custo monetário);
- gasta uma hora a produzir produto (seja ele ou );
- pode gastar no máximo horas a produzir ;
- pode gastar no máximo horas a produzir ;
- ao todo, a combinação das horas gastas para produzir e não pode ultrapassar horas.
Podemos procurar escrever o problema apresentado como um programa linear, tal que:
Chamemos a este programa o programa Primal. A aplicação do algoritmo Simplex neste programa levaria a uma solução ótima com . Podemos, contudo, tentar perceber de maneira diferente no problema: e se o nosso objetivo for minimizar as horas de produção despendidas, tendo em conta certas margens de lucro que queremos ter? A dualidade entra precisamente aqui: vamos procurar construir um outro programa, o respetivo programa Dual, que procurará representar esse mesmo espelho de restrições:
A solução ótima também seria aqui : leva ao objetivo , que bate certo com a solução ótima do programa Primal.
Há uma mnemónica relativamente simples a ter em conta para esta transformação:
Primal | Dual |
---|---|
Variáveis | Restrições |
Restrições | Variáveis |
Max | Min |
A regra, pegando então na representação matricial do programa, é a seguinte:
Pela Dualidade Fraca, sendo uma solução do programa Primal e uma solução do respetivo Dual, então
Podemos olhar para o que está a acontecer através de um esboço das hipotéticas regiões exequíveis de um programa Primal e respetivo Dual:
Fica então aparente que os programas são em tudo semelhantes (em espelho): um deles procura minimizar um dado objetivo, o Dual, e vai minimizá-lo tanto quanto possível, até um ponto em que coincide com a solução ótima do programa Primal.
Por fim, olhamos para a Dualidade Forte, que nos diz que:
-
a solução ótima do programa Primal coincide com a solução ótima do programa Dual, caso ela exista;
-
o programa Primal tem solução se e apenas se o respetivo Dual tem solução.