En esta publicación, veremos cómo Flexport utiliza las matemáticas y la ciencia de datos para resolver problemas de entrega y entregar productos a tiempo al menor costo posible.Considere un escenario especulativo: el reenviador tiene diez salidas y un vuelo de destino para cualquier envío. La única decisión que se debe tomar es si asignar cada envío a este solo vuelo. Si no asignamos una carga específica al vuelo, supongamos que es posible moverlo de otra manera.Cada envío tiene volumen y costo, y el vuelo tiene un volumen limitado. Puede considerarlo como un problema de mochila simplificado. Entonces, hay 1024-1 = 1023 posibles soluciones (no enviaríamos el avión completamente vacío).Podríamos crear una hoja de cálculo para enumerar la solución completa y elegir la más rentable. Pero, ¿qué pasa si tiene las mismas diez salidas, pero dos vuelos? Esto es 59,049 soluciones en solo 10 envíos.Un reenviador grande tiene más de diez salidas y, por supuesto, más de dos vuelos para elegir, lo que lleva a billones a billones de posibles soluciones. Entre ellos, solo millones son factibles, lo que significa que el método tradicional de hoja de cálculo puede encontrar al menos una solución factible. Pero no necesitamos solo soluciones viables. Necesitamos las mejores soluciones óptimas. ¿Cómo encontrarlos entre innumerables posibilidades? Una respuesta es usar programación entera.La programación de enteros es una subsección de optimización discreta, un área de estudio de operaciones relacionadas con la minimización de alguna función objetivo sujeta a restricciones. Queremos minimizar los costos totales, siempre que los productos se entreguen a tiempo en los lugares correctos, apilados en ULD (Dispositivo de carga unitaria, un medio de embalaje de productos). Nos esforzamos por encontrar una solución óptima, pero en la práctica a veces no podemos lograrla. En este caso, estamos satisfechos con una solución buena o cercana. Aquí nos restringimos a un modelo simple en el que se puede lograr la solución óptima.El primer paso para resolver este problema es recurrir a la literatura. La comunidad científica ha estado lidiando con el transporte de carga durante muchos años. Encontramos varios trabajos que recuerdan mucho nuestro problema. Tomamos muchos de los siguientes conceptos y notación de estos trabajos y agradecemos a los autores por su contribución a esta área.Comenzamos definiendo la función objetivo. Para minimizar los costos, necesitamos comprender el concepto de peso estándar. En resumen, el peso estándar es el peso mínimo con el que el transportista acepta trabajar, independientemente del peso que se ofrezca realmente. Tenemos peso total, peso estándar y probabilidades de sobrecargas y, por el contrario, bajo peso. El peso estándar multiplicado por el peso insuficiente es una subestimación, por lo que podemos ignorar el peso insuficiente y centrarnos en el coeficiente de sobrecarga multiplicado por la propia sobrecarga.La función objetivo es minimizar el costo total, definido como el peso total de todos los bienes asignados por ULD, multiplicado por el factor de carga. Por ejemplo, si ULD1 tiene 100 kilogramos de congestión y la tasa de congestión para ULD1 es de $ 4 por kilogramo, entonces el costo total de ULD 1 será de $ 400. Entonces, necesitamos alguna notación por sobrepeso y por su valor.Permitir es el peso de ULD j por encima del estándar y - factor de costo para el mismo ULD. Necesitamos calcular para todos . Si , entonces la función objetivo será . Se derrumba a . Queremos minimizar el valor, por lo que nuestro objetivo final:
Valor por no es un valor calculado. Este parámetro se obtiene de una hoja de cálculo o base de datos. Pero lo definimos como el peso de sobrecarga total para ULD , que podemos calcular como el peso total de todos los suministros asignados por ULD (denotarlo ), menos el peso estándar de este ULD. El peso estándar es específico para el tipo ULD y también es un parámetro. Permitir es el peso estándar para ULD en kilogramos. Luego, la cantidad de peso extra para ULD define como .El peso total de la ULD, por supuesto, depende de los pesos asignados a la ULD y su peso. Por lo tanto, necesitamos una expresión para calcularlo, incluidos los detalles mencionados anteriormente.Esto es simplemente la suma de los pesos asignados por ULD. ¿Cómo indicar que se ha asignado un lote de productos a un ULD específico? Para hacer esto, no necesitamos un parámetro, sino una variable de solución. Una variable de decisión es algo que el solucionador puede controlar mientras minimiza la función objetivo.Deja que el parámetro representa el peso bruto de la carga en kilogramos.Por ejemplo, significa que la carga 4 pesa 500 kilogramos.Permitir es una variable de decisión que toma el valor 1, si el envío asignaron ULD y contrario. Por lo tanto, cuando queremos contar todos los envíos asignados por ULD 3, podemos recorrer todas las variables , donde . Si tuviéramos 4 envíos, y los números de envío 1 y 3 se asignaron a ULD 3, se vería así:
Pero necesitamos peso total, no cantidad. Para obtenerlo, simplemente puede multiplicar cada variable de solución por un parámetro de peso. Como la variable de decisión toma el valor 0, si no se le asigna un peso, este peso se restablece y no se incluye en el total. Suponga que los pesos para las cargas uno a cuatro son 10, 50, 25 y 5. Entonces el peso total en ULD 3 será:
Escribamos este cálculo del peso total en general. Determine el peso total de la ULD como . Entoncesy . Podemos colapsar esto usando la notación de suma y . Ya que queremos que esto sea cierto para todos los posibles, usamos el signo "para todos": . Esto nos da la forma final de nuestro límite de peso total:
Peso extra
Ahora que tenemos el peso total, podemos aplicar nuestra fórmula para la carga adicional:
Por ejemplo, si y y luego peso extra kilogramos Multiplique esto por el factor de costo para obtener el resultado en dólares. A primera vista, esto puede parecer suficiente, pero ¿qué pasa con el caso cuando el peso total de la carga completa para ULD no excede el peso estándar? En este caso, si utilizamos la fórmula "tal cual", entonces el peso de sobrecarga sería un número negativo. Por ejemplo, si el peso estándar es de 1650 kilogramos y el peso total asignado es de 1000 kilogramos, entonces sobrecarga = 1000–1650 = -650. La función objetivo multiplica este número por un factor para sobrecargas y obtenemos un número negativo. Como si el transportista nos pagara por un envío inferior al costo de un peso estándar.Esto es lo que realmente queremos:.Esto es lo mismo que simplemente establecer 0 para una variable, que es tan simple como crear una restricción., Entonces, implementamos la función max () en programación matemática: a = max (b, c), es decir, a> = b && a> = c. Veamos nuestras definiciones.Función objetiva:: Relación de sobrecarga ULD : Sobrecarga ULD ; UjP: Peso estándar ULD jgi: Peso bruto de envío ixi,j: decisión variable; xi,j=1si envío iasignado por ULD j, 0de otra manera.yj: peso total de todos los envíos designados por ULD j; yj=∑i∈Igixi,j∀j∈JCada carga debe volar
En este punto, podríamos escribir esto en Python y enviarlo al solucionador. Si hiciéramos esto, encontraríamos que el solucionador asignó cero entregas a cualquier vuelo, y podemos entender rápidamente por qué: la mejor manera de minimizar la función objetivo es no acumular ningún costo. Esto lleva a la siguiente restricción: cada lote debe asignarse a algún ULD. Extendiremos esto a cada carga que debe asignarse a un único ULD, aunque en realidad podemos dividir la carga en varios ULD e incluso en varios vuelos.Esto significa quexi,jsolo puede ser 1 por un valor único de $ j $. Por ejemplo, si estamos considerando enviar 13, entoncesx13,1+x13,2+x13,3+…+x13,J=1. Queremos aplicar esta restricción para cada envío.i (que escribimos como ∀i∈I), y podemos contraer la suma usando el operador de suma (∑), por lo que nuestra limitación final es:
∑j∈Jxi,j=1∀i∈I
Dada esta limitación, el solucionador realmente comenzará a asignar envíos a vuelos, pero simplemente distribuirá los envíos entre todos los vuelos disponibles hasta que se cumplan los pesos. El solucionador colocaría cada envío restante en un ULD con el menor costo adicional. Excluyendo el volumen o peso de este ULD. Por lo tanto, las siguientes restricciones adicionales son el volumen y el peso.Limitaciones de volumen y peso
Vamos a decidir UjMcomo carga máxima en kilogramos de ULD jy Ujcomo volumen máximo en metros cúbicos ULD j. Vamos a decidirviy el volumen en metros cúbicos de carga i. Para limitar el modelo a la capacidad de carga máxima y al volumen máximo, tenemos las condiciones:
yj<=UjM∀j∈J
∑i∈Ixi,jvi<=UjV∀j∈J
Un veterano de la industria se dará cuenta de inmediato de que esto no es cierto. ¿Por qué? Debido a que estas restricciones tratan la carga como si pudiera verterse, como agua, en cualquier volumen. En realidad, las cargas son rígidas o tienen otras restricciones, como el apilamiento. No se pueden embalar 10 metros cúbicos de carga en un volumen arbitrario igual a exactamente 10 metros cúbicos. Para hacer frente a estos casos, debe resolver el problema del embalaje en contenedores. Verificamos si ciertos volúmenes caben dentro de otros, pero esto está más allá del alcance de este artículo.Ahora el solucionador asigna el ULD de envío, respetando el peso y el volumen máximos y minimizando el costo total. Pero hay otro problema: no dijimos nada sobre la cita de los artículos y el cumplimiento de las fechas de entrega. De hecho, hay cuatro marcas de tiempo que debe tener en cuenta al asignar un envío: elmomento en que el envío está realmente listo para consolidarse con otros envíos en el ULD y cargar en el vuelo. Vamos a usarQi−para representar el momento en que el envío está listo i.El tiempo durante el cual las mercancías deben descargarse en el destino se desconsolida y está disponible para su recepción, por regla general, desde la terminal de carga. Usemos $ Q_i ^ + $ para representar la fecha límite de entregai.El momento en que toda la carga debe ser entregada a la terminal de carga para cargarla en el vuelo. Vamos a usarTj−para representar el tiempo de recorte de la carga ULD $ j $.Hora de llegada del vuelo: es el momento en que la carga del vuelo está disponible en el almacén de destino. Vamos a usarTj+ para representar la hora de llegada del vuelo ULD jOrigen y Destino
Introducir un conjunto más Ji, que definimos como un conjunto de vuelos, cuyo origen y destino coinciden con la salida y recepción de carga i. En otras palabras, si la salida 85 es desde Hong Kong y el destino en Londres, entonces $ J_ {85} $ es el conjunto de todos los ULD con salida desde Hong Kong y destino en Londres.Ahora podemos usarj∈Ji para obtener un kit ULD que coincida con la ruta comercial de envío io podemos usar j∉Jipara obtener un conjunto de ULD que no coincida. Para prohibir la vinculación de la carga a ULD, que no corresponde a su origen y destino, simplemente restringimosxi,j=0para todos esos vuelos. La restricción se describe completamente como sigue:
∑j∉Jixi,j=0∀i∈I
El tiempo de entrega
Cuando se trabaja con marcas de tiempo en la optimización, es conveniente presentar los momentos necesarios como hora Unix. Ventajas del enfoque:- Almacenamiento de fechas como números.
- No es necesario manejar zonas horarias.
- El solucionador utiliza comparaciones directas más y menos.
El vuelo debe llegar antes de la hora de entrega:∑j∈JiTj+xi,j<=Qi+∀i∈ITenga en cuenta que, al igual que para la restricción anterior de salida y destino, hemos indicado que esta restricción se aplica solo a la ULD en el conjunto Jidónde Ji es un conjunto de ULD que coinciden con el envío y la recepción del envío i. ¡Eso es todo! Veamos todo el modelo.Modelo completo
Bajo tales restricciones, el solucionador asigna los artículos ULD de la manera menos costosa, y cada carga se entrega desde el lugar de salida correcto al destino correcto. Por supuesto, la carga se entrega a tiempo y sin sobrecargar los artículos por volumen o peso.Parámetros
I: Un conjunto de todos los envíos. Un envío enviadoiJ: Un conjunto de todos los ULD. ULD individual es minúsculaj.Ji: Un conjunto de todos los ULD que coinciden con el emisor y el receptor i.gi: Peso bruto de envío ien kilogramosvi: Volumen en metros cúbicos de envío icjE: Relación de sobrecarga ULD jen dólares estadounidenses por kilogramoUjM: Capacidad máxima de peso ULD jen kilogramosUjV: Potencia envolvente máxima ULD jen metros cúbicosUjP: Peso estándar ULD jen kilogramosTj−: Tiempo de entrega permitido al terminal ULD j.Tj+: Hora de llegada de ULD j.Qi−: Tiempo de envío listo i. Qi+: Período de entrega i.Variables de decisión y función objetivo:yj: Peso total ULD jyjE: Sobrecarga ULD jen kilogramosxi,j: 1 si envía iasignado por ULD j0 de lo contrario.Función de destino
Minimize∑yjEcjE
Limitaciones
yj - peso total de los envíos en ULD $ j $: yj=∑i∈Igixi,j∀j∈JEl peso extra jE es máximo (0, yj-UjP):yjE>=yj−UjP∀j∈JyjE>=0∀j∈JCada entrega debe asignarse exactamente a 1 ULD: ∑j∈Jxi,j=1∀i∈I.ULDj no puede exceder el peso máximo: yj<=UjM∀j∈JULD jno puede exceder la capacidad máxima: ∑i∈Ixi,jvi<=UjV∀j∈JPasos adicionales
Este artículo describe la programación matemática que subyace en el propósito de la carga del vuelo. Pero escribir matemáticas no es suficiente. El siguiente paso es implementar el programa, probablemente en AML, como Pyomo, o usando su propia API de solucionador, por ejemplo, Python Gurobi API. Después de eso, el desarrollador escribirá un código para transferir los parámetros de todas las salidas y vuelos disponibles. Luego, la instancia del modelo se envía al solucionador. El solucionador establece los valores de las variables de decisión de manera óptima. Luego, el desarrollador debe hacer algo con los valores de las variables de decisión.