Primeros pasos con las herramientas OR para Java

En las siguientes secciones, aprenderás a usar las herramientas OR para Java:

¿Qué es un problema de optimización?

El objetivo de la optimización es encontrar la mejor solución para un problema de un amplio conjunto de soluciones posibles. (A veces, estarás satisfecho con encontrar una solución factible; las herramientas OR también pueden hacerlo).

Este es un problema de optimización típico. Supongamos que una empresa de transporte entrega paquetes a sus clientes mediante una flota de camiones. Todos los días, la empresa debe asignar paquetes a los camiones y, luego, elegir una ruta para que cada camión entregue los paquetes. Cada asignación posible de paquetes y rutas tiene un costo, según la distancia total de viaje de los camiones y posiblemente otros factores. El problema es elegir las asignaciones de paquetes y rutas que tienen el menor costo.

Como todos los problemas de optimización, este tiene los siguientes elementos:

  • El objetivo: la cantidad que deseas optimizar. En el ejemplo anterior, el objetivo es minimizar el costo. Para configurar un problema de optimización, debes definir una función que calcule el valor del objetivo para cualquier solución posible. Esto se denomina función objetivo. En el ejemplo anterior, la función objetiva calcularía el costo total de cualquier asignación de paquetes y rutas.

    Una solución óptima es aquella para la que el valor de la función objetiva es el mejor. ("Muy bueno" puede ser un máximo o un mínimo).

  • Las restricciones: restricciones sobre el conjunto de soluciones posibles, según los requisitos específicos del problema. Por ejemplo, si la empresa de transporte no puede asignar paquetes superiores a un peso determinado a los camiones, esto impone una restricción para las soluciones.

    Una solución posible es aquella que cumple con todas las restricciones determinadas para el problema, sin ser óptima.

El primer paso para resolver un problema de optimización es identificar el objetivo y las restricciones.

Resolver un problema de optimización en Java

A continuación, se brinda un ejemplo de un problema de optimización y se muestra cómo configurarlo y resolverlo en Java.

Un ejemplo de optimización lineal

Una de las áreas de optimización más antiguas y más usadas es la optimización lineal (o la programación lineal), en la que la función objetiva y las restricciones se pueden escribir como expresiones lineales. Aquí hay un ejemplo simple de este tipo de problema.

Maximiza 3x + y sujeto a las siguientes restricciones:

  1. 0 ≤ x ≤ 1
  2. 0 ≤ y ≤ 2
  3. x + y ≤ 2

La función objetiva de este ejemplo es 3x + y. Tanto la función objetiva como las restricciones se proporcionan mediante expresiones lineales, lo que hace que este sea un problema lineal.

Pasos principales para resolver el problema

En cada idioma, los pasos básicos para configurar y resolver un problema son los mismos:

  1. Importa las bibliotecas requeridas,
  2. Declara el solucionador
  3. Crea las variables,
  4. Definir las restricciones,
  5. Define la función objetiva,
  6. Invoca el solucionador y
  7. Muestra los resultados.

Programa Java<

En esta sección, se explica un programa de Java que configura y resuelve el problema.

A continuación, se indican los pasos que debes seguir:

  • Importa las bibliotecas requeridas.
    import com.google.ortools.Loader;
    import com.google.ortools.init.OrToolsVersion;
    import com.google.ortools.linearsolver.MPConstraint;
    import com.google.ortools.linearsolver.MPObjective;
    import com.google.ortools.linearsolver.MPSolver;
    import com.google.ortools.linearsolver.MPVariable;
  • Declara el solucionador.
    // Create the linear solver with the GLOP backend.
    MPSolver solver = MPSolver.createSolver("GLOP");
    if (solver == null) {
      System.out.println("Could not create solver GLOP");
      return;
    }
    MPSolver es un wrapper para resolver cualquier problema de programación lineal o programación de números enteros mixtos.
  • Crea las variables.
    // Create the variables x and y.
    MPVariable x = solver.makeNumVar(0.0, 1.0, "x");
    MPVariable y = solver.makeNumVar(0.0, 2.0, "y");
    
    System.out.println("Number of variables = " + solver.numVariables());
  • Define las restricciones. Las primeras dos restricciones, 0 &leq; x1 y 0 &leq; y2, ya están establecidas por las definiciones de las variables. En el siguiente código, se define la restricción x + y &leq; 2:
    double infinity = Double.POSITIVE_INFINITY;
    // Create a linear constraint, x + y <= 2.
    MPConstraint ct = solver.makeConstraint(-infinity, 2.0, "ct");
    ct.setCoefficient(x, 1);
    ct.setCoefficient(y, 1);
    
    System.out.println("Number of constraints = " + solver.numConstraints());
    El método setCoefficient establece los coeficientes de x y y en la expresión de la restricción.
  • Define la función objetiva.
    // Create the objective function, 3 * x + y.
    MPObjective objective = solver.objective();
    objective.setCoefficient(x, 3);
    objective.setCoefficient(y, 1);
    objective.setMaximization();
    El método setMaximization declara que se trata de un problema de maximización.
  • Invoca el solucionador y muestra los resultados.
    System.out.println("Solving with " + solver.solverVersion());
    final MPSolver.ResultStatus resultStatus = solver.solve();
    System.out.println("Status: " + resultStatus);
    if (resultStatus != MPSolver.ResultStatus.OPTIMAL) {
      System.out.println("The problem does not have an optimal solution!");
      if (resultStatus == MPSolver.ResultStatus.FEASIBLE) {
        System.out.println("A potentially suboptimal solution was found");
      } else {
        System.out.println("The solver could not solve the problem.");
        return;
      }
    }
    
    System.out.println("Solution:");
    System.out.println("Objective value = " + objective.value());
    System.out.println("x = " + x.solutionValue());
    System.out.println("y = " + y.solutionValue());

Completar programa

A continuación, se muestra el programa completo.

package com.google.ortools.linearsolver.samples;

import com.google.ortools.Loader;
import com.google.ortools.init.OrToolsVersion;
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPObjective;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;

/** Minimal Linear Programming example to showcase calling the solver. */
public final class BasicExample {
  public static void main(String[] args) {
    Loader.loadNativeLibraries();

    System.out.println("Google OR-Tools version: " + OrToolsVersion.getVersionString());

    // Create the linear solver with the GLOP backend.
    MPSolver solver = MPSolver.createSolver("GLOP");
    if (solver == null) {
      System.out.println("Could not create solver GLOP");
      return;
    }

    // Create the variables x and y.
    MPVariable x = solver.makeNumVar(0.0, 1.0, "x");
    MPVariable y = solver.makeNumVar(0.0, 2.0, "y");

    System.out.println("Number of variables = " + solver.numVariables());

    double infinity = Double.POSITIVE_INFINITY;
    // Create a linear constraint, x + y <= 2.
    MPConstraint ct = solver.makeConstraint(-infinity, 2.0, "ct");
    ct.setCoefficient(x, 1);
    ct.setCoefficient(y, 1);

    System.out.println("Number of constraints = " + solver.numConstraints());

    // Create the objective function, 3 * x + y.
    MPObjective objective = solver.objective();
    objective.setCoefficient(x, 3);
    objective.setCoefficient(y, 1);
    objective.setMaximization();

    System.out.println("Solving with " + solver.solverVersion());
    final MPSolver.ResultStatus resultStatus = solver.solve();

    System.out.println("Status: " + resultStatus);
    if (resultStatus != MPSolver.ResultStatus.OPTIMAL) {
      System.out.println("The problem does not have an optimal solution!");
      if (resultStatus == MPSolver.ResultStatus.FEASIBLE) {
        System.out.println("A potentially suboptimal solution was found");
      } else {
        System.out.println("The solver could not solve the problem.");
        return;
      }
    }

    System.out.println("Solution:");
    System.out.println("Objective value = " + objective.value());
    System.out.println("x = " + x.solutionValue());
    System.out.println("y = " + y.solutionValue());

    System.out.println("Advanced usage:");
    System.out.println("Problem solved in " + solver.wallTime() + " milliseconds");
    System.out.println("Problem solved in " + solver.iterations() + " iterations");
  }

  private BasicExample() {}
}

Ejecuta el programa Java

Puedes ejecutar el programa anterior de la siguiente manera:

  1. Copia y pega el código anterior en el archivo nuevo y guárdalo como my_program.java.
  2. Abre una ventana de comandos en el nivel superior del directorio en el que instalaste OR-Tools y, luego, ingresa lo siguiente:
    make run SOURCE=relative/path/to/my_program.java
    , donde relative/path/to/ es la ruta de acceso al directorio donde guardaste el programa.

El programa muestra los valores de x y y que maximizan la función objetivo:

Solution:
x =  1.0
y =  1.0

Para compilar el programa sin ejecutarlo, ingresa lo siguiente:

make build SOURCE=relative/path/to/my_program.java

Más ejemplos de Java

Para obtener más ejemplos de Java que ilustran cómo resolver varios tipos de problemas de optimización, consulta Ejemplos.

Identificar el tipo de problema que deseas resolver

En el mundo, existen muchos tipos diferentes de problemas de optimización. Para cada tipo de problema, existen diferentes enfoques y algoritmos para encontrar una solución óptima.

Antes de comenzar a escribir un programa para resolver un problema de optimización, debes identificar con qué tipo de problema se trata y, luego, elegir un solucionador adecuado: un algoritmo para encontrar una solución óptima.

A continuación, encontrarás una breve descripción general de los tipos de problemas que resuelven las herramientas del operador OR y vínculos a las secciones de esta guía que explican cómo resolver cada tipo de problema.

Optimización lineal

Como aprendiste en la sección anterior, un problema de optimización lineal es uno en el que la función objetivo y las restricciones son expresiones lineales en las variables.

El solucionador principal de las herramientas OR para este tipo de problema es el solucionador de optimización lineal, que en realidad es un wrapper para varias bibliotecas diferentes destinadas a la optimización de números enteros mixtos y a las bibliotecas de terceros.

Más información sobre la optimización lineal

Optimización de números enteros mixtos

Un problema de optimización de números enteros mixtos es aquel en el que se requiere que algunas o todas las variables sean números enteros. Un ejemplo es el problema de asignación, en el que se necesita asignar un grupo de trabajadores a un conjunto de tareas. Para cada trabajador y tarea, se define una variable cuyo valor es 1 si ese trabajador está asignado a la tarea en cuestión y 0 en el caso contrario. En este caso, las variables solo pueden tomar los valores 0 o 1.

Más información sobre la optimización de números enteros mixtos

Optimización de restricciones

La optimización de restricciones, o programación de restricciones (CP), identifica soluciones posibles a partir de un gran conjunto de candidatos, en las que el problema se puede modelar en términos de restricciones arbitrarias. La CP se basa en la viabilidad (encontrar una solución factible) en lugar de en la optimización (encontrar una solución óptima) y se centra en las restricciones y las variables, en lugar de la función objetiva. Sin embargo, la CP se puede usar para resolver problemas de optimización con solo comparar los valores de la función objetiva para todas las soluciones posibles.

Más información sobre la optimización de restricciones

Assignment

Los problemas de asignación implican la asignación de un grupo de agentes (por ejemplo, trabajadores o máquinas) a un conjunto de tareas, en el que hay un costo fijo para asignar cada agente a una tarea específica. El problema es encontrar la tarea con el menor costo total. Los problemas de asignación son, en realidad, un caso especial de problemas de flujo de red.

Más información sobre las asignaciones

Empaquetado

El empaquetado de contenedores se refiere al problema de empaquetar un conjunto de objetos de diferentes tamaños en contenedores con distintas capacidades. El objetivo es empaquetar la mayor cantidad posible de objetos, sujeto a la capacidad de los contenedores. Un caso especial de esto es el problema de Knapsack, en el que solo hay un contenedor.

Más información sobre el empaquetado en contenedores

Programación

Los problemas de programación implican la asignación de recursos para realizar un conjunto de tareas en momentos específicos. Un ejemplo importante es el problema de la tienda de trabajo, en el que varios trabajos se procesan en varias máquinas. Cada trabajo consiste en una secuencia de tareas que deben realizarse en un orden determinado y cada tarea debe procesarse en una máquina específica. El problema es asignar un programa para que todos los trabajos se completen en el menor intervalo de tiempo posible.

Más información sobre la programación

Enrutamiento

Los problemas de enrutamiento implican encontrar las rutas óptimas para que una flota de vehículos atraviese una red, definida por un grafo dirigido. El problema de asignar paquetes a camiones de reparto, que se describe en la sección ¿Qué es un problema de optimización?, es un ejemplo de un problema relacionado con el enrutamiento. Otro es el problema del vendedor que viaja.

Más información sobre el enrutamiento

Flujos de red

Muchos problemas de optimización se pueden representar con un grafo dirigido que consta de nodos y arcos dirigidos entre ellos. Por ejemplo, los problemas de transporte, en los que los productos se envían a través de una red ferroviaria, se pueden representar con un gráfico en el que los arcos son líneas ferroviarias y los nodos son centros de distribución.

En el problema de flujo máximo, cada arco tiene una capacidad máxima que se puede transportar por él. El problema es asignar la cantidad de bienes que se enviarán en cada arco para que la cantidad total que se transporte sea lo más grande posible.

Más información sobre los flujos de red