Programación de enteros de Python: método de ramificación y límite

        El método de bifurcación y acotación se puede utilizar para resolver problemas de programación de enteros puros o enteros mixtos. Propuesto por Land Doig y Dakin et al., a principios de la década de 1960. Debido a que este método es flexible y fácil de resolver por computadora, ahora es un método importante para resolver la programación entera. Se ha aplicado con éxito para resolver el problema del programa de producción, el problema del viajante de comercio, el problema de ubicación de la fábrica, el problema de la mochila y el problema de asignación.


método de ramificación y límite

1. Definición

        Una búsqueda sistemática se realiza correctamente en todos los espacios de soluciones factibles de un problema de optimización con restricciones (cuyas soluciones factibles son finitas), que es el contenido de la ramificación y la delimitación. Por lo general, todo el espacio de soluciones factibles se divide repetidamente en subconjuntos cada vez más pequeños, lo que se denomina ramificación; y se calcula un límite inferior del objetivo (para el problema de mínimos) para el conjunto de soluciones en cada subconjunto, lo que se denomina delimitación. Después de cada bifurcación, aquellos subconjuntos cuyos límites excedan el valor objetivo del conjunto de soluciones factibles conocido no se bifurcarán más, por lo que se pueden ignorar muchos subconjuntos, lo que se denomina poda. Esta es la idea principal del método branch andbound.

        Existe un problema A de programación entera maximizadora, y su correspondiente programación lineal es el problema B. Partiendo de resolver el problema B, si su solución óptima no cumple las condiciones enteras de A, entonces la función objetivo óptima de B debe ser la función objetivo óptima de A. El límite superior de la función objetivo óptima de ^ {*} se denota como \overline{z} , y el valor de la función objetivo de cualquier solución factible de A de ^ {*}será un límite inferior \ subrayado {z}de . El método de ramificación y límite es un método para dividir la región factible de B en subregiones. Gradualmente disminuya \overline{z}y aumente \ subrayado {z}, y finalmente encuentre de ^ {*}. Ahora use el siguiente ejemplo para ilustrar:

2. Explicación de ejemplos

Ejemplo 3 Resuelve el siguiente programa entero:

 \begin{aligned} &\operatorname{Max} \quad z=40 x_{1}+90 x_{2} \\ &\left\{\begin{array}{l} 9 x_{1}+7 x_{ 2} \leq 56 \\ 7 x_{1}+20 x_{2} \leq 70 \\ x_{1}, x_{2} \geq 0 \ \end{matriz}\right.  \end{alineado}y es un entero 

        (i) Despreciando la restricción de enteros, es decir, resolviendo la correspondiente programación lineal B, la solución óptima es:

El uso de la tabla simplex de python produce:

        objctive        |                   355.88                   |
        solution        |  4.81  |  1.82  |   0    |   0    |

La solución óptima es:x_{1}=4,81, x_{2}=1,82, z=355,88

                        Se puede observar que no cumple la condición de número entero. En este momento Cones el A límite superior del valor óptimo de la función objetivo del problema, denotado como . obviamente es una solución entera factible del problema, en este momento , es un límite inferior de, denotado por , es decir .de ^ {*}\overline{z}x_{1}=0,x_{2}=0Az = 0de ^ {*}\ subrayado {z}0\leq z^{*}\leq 356

        (ii) Debido a que x_{1},x_{2}los actuales son todos no enteros, no cumplen con los requisitos de enteros, así que elija uno de ellos para ramificar. Sea 1 x elegido para la ramificación y divida el conjunto factible en 2 subconjuntos:

x_{1}\leq [4,81]=4,x_{1}\geq [4,81]+1=5

Dado que no hay números enteros entre 4 y 5, las soluciones enteras para estos dos subconjuntos deben coincidir con las soluciones enteras originales del conjunto factible. Este paso se llama ramificación. La planificación y soluciones de estos dos subconjuntos son las siguientes:

Pregunta B_{1}:

\begin{alineado} &\quad \operatorname{Max} \quad z=40 x_{1}+90 x_{2} \\ &\left\{\begin{array}{l} 9 x_{1}+7 x_{2} \leq 56 \\ 7 x_{1}+20 x_{2} \leq 70 \\ 0 \leq x_{1} \leq 4, x_{2} \geq 0 \end{matriz}\right .  \end{alineado}

La solución óptima es:x_{2}=4,0,x_{2}=2,1,z_{1}=349

Pregunta B_{2}:

\begin{alineado} & \quad \text { Max } \quad z=40 x_{1}+90 x_{2} \\ &\left\{\begin{array}{l} 9 x_{1}+7 x_{2} \leq 56 \\ 7 x_{1}+20 x_{2} \leq 70 \\ x_{1} \geq 5, x_{2} \geq 0 \end{matriz}\right.  \end{alineado}

 La solución óptima es:x_{2}=5,0,x_{2}=1,57,z_{1}=341,4

 Redefinir:0\leq z^{*}\leq 349

        (iii) Ramifique el problema B1 para obtener los problemas B11 y B12, y sus soluciones óptimas son

\begin{matriz}{ll} B_{11}: & x_{1}=4, x_{2}=2, z_{11}=340 \\ B_{12}: & x_{1}=1,43, \ matemática{x}_{2}=3,00, z_{12}=327,14 \end{matriz}

Rebote: 340\leq z^{*}\leq 341, y B_{12}podaremos.

        (iv B_{2}) Ramifica el problema para obtener el problema B_{21}y B_{22}, y sus soluciones óptimas son

B_{21}:x_{1}=5,44,x_{2}=1,00,z_{22}=308.

                                        ​​​​​​​Ninguna  B_{22}:  solución factible.

Será B_{21},B_{22} podado.

Por lo que se puede concluir que la solución óptima al problema original es:

x_{1}=4,x_{2}=2,z^{*}=340
 

3. Proceso de modelado matemático:

El método de bifurcación y acotación resuelve el problema de programación entera (maximización) de la siguiente manera:

Inicialmente, el problema de programación entera a resolver se llama problema A, y el problema de programación lineal correspondiente se llama problema B.

Resolver el problema B puede producir una de las siguientes situaciones:

(a) Si B no tiene una solución factible, entonces A tampoco tiene una solución factible, entonces deténgase.

(b) B tiene una solución óptima y cumple la condición de número entero del problema A, la solución óptima de B es la solución óptima de A, entonces deténgase.

(c) B tiene una solución óptima, pero no cumple la condición de número entero del problema A, registre el valor de su función objetivo \overline{z}.

Usa la observación para encontrar una solución entera factible para el problema A. Por lo general, es recomendable x_{j}=0,j=1,...,n,  tratar de encontrar el valor de su función objetivo y registrarlo como \ subrayado {z}. Usando  de ^ {*}​​​​​​​trate de encontrar el valor de su función objetivo y regístrelo como \subrayado{z}\leq z^{*}\leq \overline{z}iteración.

proceso de modelado

        El primer paso: rama, elige una variable x_{j}que no cumpla la condición de entero en la solución óptima de B, y su valor se representa b_{j}por [  b_{j}] para representar b_{j}el entero más grande menor que . Construir dos restricciones

 x_{j}\leq [b_{j}]yx_{j}\geq [b_{j}]+1

Agregue estas dos restricciones al problema B respectivamente y encuentre dos problemas de programación sucesores B_{1}y B_{2}. Resuelva estos dos problemas de sucesores sin considerar la condición de número entero.

        Delimitación, toma cada problema subsiguiente como una rama para indicar el resultado de la solución, y encuentra el que tiene el mayor valor de la función objetivo óptima como el nuevo límite superior entre los resultados de las soluciones de otros problemas \overline{z}. De cada rama que haya cumplido la condición de número entero, encuentre el valor máximo de la función objetivo como el nuevo límite inferior \ subrayado {z}, si no hay efecto, \ subrayado {z}permanecerá sin cambios .

         Paso 2: Comparar y podar Si alguna función objetivo óptima de cada rama es menor que z, podar esta rama, es decir, no se considerará en el futuro. Si es mayor que \ subrayado {z}y no cumple la condición de número entero, repita el primer paso. hasta que obtengas de ^ {*}= al \ subrayado {z}final. para obtener la solución entera óptima x_{j}^{*}, j=1,...,n.

 

 

 4. Implementación de la programación

Use python para implementar el algoritmo de ramificación y límite:

from scipy.optimize import linprog
import numpy as np
from math import floor, ceil
import copy


class Node(object):
    def __init__(self, x_bounds=[], freeze_var_list=[], index=0, upper_or_lower=0):
        self._x_bounds = x_bounds
        self._freeze_var_list = freeze_var_list
        self._index = index
        self._upper_or_lower = upper_or_lower

        print("创建节点: {}".format(index))
        print('')

    def freeze_var(self, index, val):
        self._x_bounds[index][0] = val
        self._x_bounds[index][1] = val
        self._freeze_var_list.append(index)

    def set_lp_res(self, res):
        self._res = res
        s = ""
        for l in range(len(self._res['x'])):
            if l in self._freeze_var_list:
                s += "[" + str(self._res['x'][l]) + "]"
            else:
                s += " " + str(self._res['x'][l])
        print("x: ", s)

    def check_integer_val_solved(self, m):
        return True if m == len(self._freeze_var_list) else False


class BbAlgorithm(object):
    def __init__(self, c, a_ub, b_ub, x_b, integer_val):
        self.c = c
        self.a_ub = a_ub
        self.b_ub = b_ub
        self.x_b = x_b
        self._integer_val = integer_val
        self.best_solution = float('inf')
        self.best_node = None
        self.nodes = []
        self.nodes_solution = []

    def solve_lp(self, cur_x_b):
        return linprog(self.c, A_ub=self.a_ub, b_ub=self.b_ub, bounds=cur_x_b)

    def check_fessible(self, res):
        if res['status'] == 0:
            return True
        elif res['status'] == 2:
            return False
        else:
            raise ("问题无界")

    def add_node(self, node):
        res = self.solve_lp(node._x_bounds)
        if self.check_fessible(res) and res['fun'] < self.best_solution:
            node.set_lp_res(res)
            self.nodes_solution.append(res['fun'])
            self.nodes.append(node)
            if node.check_integer_val_solved(len(self._integer_val)):
                self.best_solution = res['fun']
                self.best_node = node
                print("----------------当前解决方案-------------------")
                print("x: ", node._res['x'])
                print("z: ", node._res['fun'])
                print("---------------------------------------------------\n")
            print("==> 将节点添加到树列表: ", node._index)
            print("==> 当前节点: ", self.nodes_solution)
            print("")
            return True
        else:
            print("==> 节点不可行: ", node._index)
            print("==> 当前节点: ", self.nodes_solution)
            print("")
            return False

    def del_higher_val_node(self, z_s):
        del_list = []
        for i in range(len(self.nodes_solution)):
            if self.nodes_solution[i] >= z_s:
                del_list.append(i)
        s = ""
        for i in del_list:
            s += " " + str(self.nodes[i]._index)
        print("删除节点: ", s)
        self.nodes = list(np.delete(self.nodes, del_list))
        self.nodes_solution = list(np.delete(self.nodes_solution, del_list))
        print("当前节点: ", self.nodes_solution)
        print("")

    def del_item(self, index):
        print("删除节点: ", self.nodes[index]._index)
        self.nodes = list(np.delete(self.nodes, index))
        self.nodes_solution = list(np.delete(self.nodes_solution, index))
        print("当前节点: ", self.nodes_solution)
        print("")

    def check_bounds(self, temp_x_b, index, u_or_l):
        if u_or_l == 1:
            if self.x_b[index][0] is not None and temp_x_b[index][0] is None:
                return False
            elif self.x_b[index][0] is None and temp_x_b[index][0] is not None:
                return True
            elif self.x_b[index][0] is not None and temp_x_b[index][0] is not None:
                return False if(self.x_b[index][0] > temp_x_b[index][0]) else True
        elif u_or_l == 2:
            if self.x_b[index][1] is not None and temp_x_b[index][1] is None:
                return False
            elif self.x_b[index][1] is None and temp_x_b[index][1] is not None:
                return True
            elif self.x_b[index][1] is not None and temp_x_b[index][1] is not None:
                return False if(self.x_b[index][1] < temp_x_b[index][1]) else True
        else:
            print("界限误差")
            exit()

    def run(self):
        print("####################### 开始 B & B #####################\n")
        node_count = 0
        node = Node(copy.deepcopy(self.x_b), [], node_count)
        node_count += 1
        res = self.solve_lp(self.x_b)

        lower = floor(res['x'][self._integer_val[0]])
        upper = lower + 1

        lower_node = Node(copy.deepcopy(self.x_b), [], node_count, 1)
        lower_node.freeze_var(self._integer_val[0], lower)
        self.add_node(lower_node)
        node_count += 1

        upper_node = Node(copy.deepcopy(self.x_b), [], node_count, 2)
        upper_node.freeze_var(self._integer_val[0], upper)
        self.add_node(upper_node)
        node_count += 1

        while len(self.nodes) > 0:
            index = np.argmin(self.nodes_solution)
            x_b = self.nodes[index]._x_bounds
            freeze_list = self.nodes[index]._freeze_var_list
            res = self.nodes[index]._res
            freeze_var_index = len(freeze_list)

            lower = floor(res['x'][self._integer_val[freeze_var_index]])
            upper = lower + 1
            lower_node = Node(copy.deepcopy(x_b), copy.deepcopy(freeze_list), node_count, 1)
            lower_node.freeze_var(self._integer_val[freeze_var_index], lower)
            self.add_node(lower_node)
            node_count += 1

            upper_node = Node(copy.deepcopy(x_b), copy.deepcopy(freeze_list), node_count, 2)
            upper_node.freeze_var(self._integer_val[freeze_var_index], upper)
            self.add_node(upper_node)
            node_count += 1

            self.del_item(index)
            self.del_higher_val_node(self.best_solution)
            print("############################################################")
        print("")
        print("######################### 最佳解决方案 #######################")
        print(self.best_node._res)


if __name__ == "__main__":
    integer_val = [0,1]
    c = [-40, -90]
    A = [[9, 7], [7, 20]]
    b = [56,70]
    x_bounds = [[0, None] for _ in range(len(c))]
    bb_algorithm = BbAlgorithm(c, A, b, x_bounds, integer_val)
    bb_algorithm.run()

 Resultado de salida:

####################### 开始 B & B #####################

创建节点: 0

创建节点: 1

x:  [4.0] 2.0999999999901706
==> 将节点添加到树列表:  1
==> 当前节点:  [-348.99999999911535]

创建节点: 2

x:  [5.0] 1.5714285714280996
==> 将节点添加到树列表:  2
==> 当前节点:  [-348.99999999911535, -341.4285714285289]

创建节点: 3

x:  [4.0][2.0]
----------------当前解决方案-------------------
x:  [4. 2.]
z:  -340.0
---------------------------------------------------

==> 将节点添加到树列表:  3
==> 当前节点:  [-348.99999999911535, -341.4285714285289, -340.0]

创建节点: 4

==> 节点不可行:  4
==> 当前节点:  [-348.99999999911535, -341.4285714285289, -340.0]

删除节点:  1
当前节点:  [-341.4285714285289, -340.0]

删除节点:   3
当前节点:  [-341.4285714285289]

############################################################
创建节点: 5

==> 节点不可行:  5
==> 当前节点:  [-341.4285714285289]

创建节点: 6

==> 节点不可行:  6
==> 当前节点:  [-341.4285714285289]

删除节点:  2
当前节点:  []

删除节点:  
当前节点:  []

############################################################

######################### 最佳解决方案 #######################
     con: array([], dtype=float64)
     fun: -340.0
 message: 'The solution was determined in presolve as there are no non-trivial constraints.'
     nit: 0
   slack: array([6., 2.])
  status: 0
 success: True
       x: array([4., 2.])

Supongo que te gusta

Origin blog.csdn.net/qq_21402983/article/details/126388515
Recomendado
Clasificación