El Perceptrón – Aprendizajes hacia la IA – Parte 1

 Nuevamente con el aprendizaje hacia la Inteligencia Artificial, para ello, uno de los tópicos a revisar son las redes neuronales artificiales, estas básicamente son una representación computacional de las redes neuronales “naturales” en términos de su comportamiento; una red neuronal está compuesta por neuronas, pero ¿Qué es una neurona?, por definición:

“Célula del sistema nervioso formada por un núcleo y una serie de prolongaciones, una de las cuáles es más larga que las demás”

300px-Neurona.svg

Podríamos decir que la función básica de la neurona es un procesamiento que se da a través de recibir un input (estímulos eléctricos), realizar un procesamiento y transmitir un resultado hacia la siguiente neurona, McCulloch  y Pitts crearon la neurona que lleva su nombre y que es una unidad de cálculo que intenta modelar el comportamiento descrito de una neurona “natural“:

neuronaa

PLANTEANDO

Si describimos esta neurona las Xi representan las entradas y las Wij representan el peso asignado a cada una de las entradas que tenemos, la sumatoria representa la neurona y el axón inicialmente sin tomar el f() sería la salida, hago esto con el fin de explicarles de la misma forma que entendí la conformación de la neurona, entonces podemos decir lo siguiente:

1
Primer acercamiento

Para todas las entradas posibles la salida Y sería la suma de cada entrada multiplicada con su ponderada, pero esta ecuación no está completa aunque si ya nos permite esbozar que una red neuronal será una aproximación a una función matemática de pesos, al ser matemática implica que podemos hacer uso de toda la matemática disponible para su resolución, al expresarlo mediante una función hemos “modelado” el comportamiento, ahora tenemos que poner a prueba nuestro modelo.

Cuando deseamos comprobar si nuestro modelo es adecuado lo primero que se hace es poner a prueba nuestro modelo y que pueda responder a las funciones lógicas más básicas, entonces:

Primera Prueba – La función NOT

Una función de ese tipo en una tabla se expresa de la siguiente forma:

2

Podemos representarla de la siguiente forma:

3

 

Cuando reemplazamos los valores a la función que hemos obtenido nos queda:

4

Entonces este primer acercamiento no es correcto y debe de modificarse, ¿Cómo debe de modificarse?, pues si observamos  seria muy conveniente el agregar un término constante b (en el gráfico es θ) por cada neurona de la siguiente forma:

6
Segundo acercamiento

Entonces se puede representar de la

5

 

Resolvemos según la tabla y obtenemos:

7

Vemos que esto nos funciona cuando hacemos uso de un NOT, ahora veamos si es posible en un OR.

Segunda Prueba – función OR

La tabla de una función lógica OR es:

8

Podemos expresarlo de la siguiente forma:

9

Reemplazando tenemos:

10.png

Tal como podemos observar vamos a obtener una contradicción en el que los pesos “W” son 1 y en la última ecuación ambos suman 2 pero el resultado es 1.

REPLANTEANDO NUEVAMENTE!!! 

Si resolvemos con las demás funciones lógicas simples veríamos que tampoco se cumple, entonces ¿Qué es a lo que se llegó?, debía de implementarse una función, a la que llamaron función no lineal, para este caso desarrollaron la función step, tal que su representación final sea la del inicio:

neuronaa

Tal que la función step es:

11

Entonces validamos la función NOT:

12

Y si cumple, ahora validemos la función OR:

13

¿Qué ocurre entonces con la función AND?

Se cumple la tabla:

14

¿Cómo podemos interpretar una función AND?, pues una AND se expresa en la negación de la función OR; tal como está nuestra función no se adapta, para ello debemos de aumentar en 2 neuronas y  de hacer un cambio en nuestro parámetro y revisar si nuestro modelo actual sigue adaptándose, entonces tomaremos el valor -0.5 para el “OR” y vemos que cumple, finalmente esto se puede graficar de la siguiente forma:

16.png

Es factible usando el mismo modelo expresarlo con una sola neurona si el valor del parámetro fuese -1.5

Tercera Prueba función XOR:

El problema que XOR a de implicar el uso de una neurona más, veamos la tabla:

15

 

Esta función se expresa de la siguiente forma:

17

Si construimos la gráfica:

18.png

 

Anuncios

Matplotlib – Generador de Gráficos en Python

Una imagen es mejor que mil palabras, es un término recurrente cuando se desea resumir o explicar algún tópico, las imágenes pueden ser solo el sujeto que hemos de cambiar por un diagrama o cuadros o gráficos, imaginemos que tenemos una cantidad considerable de datos y queremos mostrar el comportamiento que estos siguen, para ello existe la librería matplotlib, el cual es una librería para la generación de gráficos a partir de datos contenidos en listas  y es usado por lo general a través de numpy, para esta breve descripción usaremos a VSCODE como editor de código, sin más que decir. empecemos:

INSTALACIÓN E IMPLEMENTACIÓN

La instalación se realiza a través del terminal (CMD si es que tu python está dentro del path) incorporado en VSCODE con la instrucción siguiente:

pip install matplotlib

Ahora debemos de abrir un fichero y en este debemos de importar la librería, se estila que la librería sea importada de la siguiente forma:

import matplotlib.pyplot as plt

matplotlib.pyplot es una colección de funciones que asemejan al estilo de comandos que tiene MATLAB, que supongo tendrá especial significancia para aquellos que ya están familiarizados con dicho software, en lo particular yo no, pero te encontrarás con muchos tutoriales que hacen referencia a este tipo de importación.

HACIENDO UNA LÍNEA RECTA

Hacer una línea recta es muy sencillo en esta librería, pues en su forma más básica debemos de tener dos listas que representen el eje X e Y y colocarlos dentro del método plot(), plot tiene un constructor amplio, pero por el momento vamos a reducirlo a la realización de un recta simple en donde requerimos tanto los elementos de las coordenadas X e Y y una etiqueta, entonces plot hace la recta pero cuando creamos un gráfico necesitamos saber que representan cada uno de los ejes, supongamos que tenemos intenciones de presentar la relación entre los metros cuadrados de una habitación con su precio de alquiler, para ello debemos de hacer uso de los métodos xlabel() y ylabel(), los  cuáles reciben como parámetro obligatorio  la cadena de texto que representa el eje en cuestión, podemos agregar un título a nuestro gráfico con title() y la leyenda que corresponde a la recta que se toma de la etiqueta al momento de su creación con el método legend(), finalmente debemos de mostrar la gráfica, y esta se muestra con el método show(), entonces:

import matplotlib.pyplot as plt

#creamos dos listas
x=[1,2,3]
y=[5,7,9]

plt.plot(x,y,label="linea1") # creación de la recta
plt.xlabel("Metros cuadrados") #etiqueta eje X
plt.ylabel("Costo en miles de soles") #etiqueta eje Y
plt.title("Relación Costo y m2") #Agregamos un título
plt.legend() #agregamos la leyenda
plt.show() #desplegamos el gráfico
El resultados es el siguiente:
mat1.png

HACIENDO UNA DIAGRAMA DE BARRAS

Para realizar un diagrama de barras es tan sencillo, ya que solo se requiere del método bar(), como requisito debemos de indicarle la serie en el eje X y en Y, una etiqueta, todo lo demás se mantiene de forma similar:

x=[2,4,6,8,10]
y=[6,7,8,2,4]

x2=[1,3,5,9,11]
y2=[7,8,2,4,2]

plt.bar(x,y,label="Bar 1")
plt.bar(x2,y2,label="Bar 2")

plt.xlabel("x")
plt.ylabel("y")

plt.title("Gráfica de Barras")
plt.legend()
plt.show()

Así de forma sucesiva podemos hacer una serie de gráficas, personalmente no creo que requiera mayor análisis que el que implique revisar la misma API de la librería.

Matrices en Python uso de Numpy

Numpy es la librería casi por defecto cuando hemos de tratar con matrices, y específicamente en el uso de big data por su menor consumo de tiempo de procesamiento computacional dando paso a una mayor cantidad de procesamiento, entonces en esta ocasión revisaremos las operaciones con matrices que se pueden realizar:

INSTALACIÓN

En primera instancia este tutorial asume que estás usando VSCODE  y que tu sistema operativo o distribución ya tiene instalado python, abrimos el terminal incorporado de VSCODE, abrimos el terminal y hacemos la siguiente instrucción:

pip install numpy

DECLARACIÓN DE UNA MATRIZ

Cuando hemos de usar numpy debemos de importar la librería en el top del fichero, entonces:

import numpy as np

 Esto significa que todo lo relacionado a la invocación de los métodos que pertenecen a numpy lo hemos de realizar haciendo “np.nombre_metodo“, como si métodos estáticos se trataran en su sintaxis (java ¿huh?).

Ahora bien, ¿Cómo se implementa una matriz en un lenguaje de programación?, pues esta se implementa a través de los arreglos, un arreglo es una estructura de datos que nos permiten almacenar elementos que dependiendo del lenguaje pueden ser del mismo tipo o no, java por ejemplo permite los arreglos de un mismo tipo y solo de primitivos si son de tipo referenciados pues tienen otros nombres; sin embargo, python considera que los elementos que conformen un arreglo no necesariamente tienen que ser todos del mismo tipo de dato (no hacemos referencia a primitivos pues todo es un objeto en python).

Python agrupa los arreglos de la siguiente forma:

Para las matrices en numpy se organiza la estructura de forma muy similar a las listas, aunque no son lo mismo, entonces hecha esta aclaración, una matriz se define de la siguiente forma:

a=np.array([1,2,3])#1 dimensión
b=np.array([[1,2,3],[4,5,6]]) #2 dimensiones
c=np.array([[1,2,3],[4,5,6],[7,8,9]]) # 3 dimensiones

El objeto principal en numpy es ndarray el cual representa un arreglo que puede ser multidimensional y es homogéneo en términos del tipo de objeto. Una de las formas de crear una matriz se realiza con el método array, y que su constructor es de la siguiente forma:

numpy.array(objectdtype=Nonecopy=Trueorder='K'subok=Falsendmin=0)

Aunque los demás términos ya están descritos en su definición, el parámetro obligatorio es un objeto el cual tal como se ha mostrado divide sus dimensiones siguiendo una estructura tipo lista. Podemos también transformar desde listas o tuplas (bueno en esencia es un objeto ¿no?), entonces sea:

arreglo=[1,2,3,4,5,6,7,8,9]
arreglo2=(1,2,3,4,5,6,7,8,9)

x=np.array(arreglo)
y=np.array(arreglo2)

y=np.reshape(y,(3,3))
x=np.reshape(x,(3,3))

Si ingresamos una lista o tupla, esta quedará como una matriz de una sola dimensión, si deseamos dar un ordenamiento específico como una matriz de 3×3, debemos de hacer uso del método reshape:

numpy.reshape(anewshapeorder='C')

Este método nos devuelve una matriz con el ordenamiento deseado, y es tal como la hemos usado, el tipo de orden debe indicar a través de una tupla, en nuestro caso de (3,3). Dicho esto como parte básica para la creación de las matrices, vamos a realizar las operaciones con ellas.

OPERACIONES BÁSICAS

Sean 2 matrices de 2×2 A y B que las obtenemos a través de listas, pero vamos a generarlo todo en una sola línea:

a=[1,2,3,4,]
b=[7,8,9,10]

a=np.reshape(np.array(a),(2,2))
b=np.reshape(np.array(b),(2,2))

La suma entonces:

c=b+a
print(c)

La resta:

c=b-a

Podemos hacer que todos los elementos de la matriz se eleven a un exponente determinado de la siguiente forma:

c=b**exponente
c=np.power(b,exponente)

Multiplicación

Las matrices tienen una particularidad al momento de la multiplicación, ya que siguen la forma de filas por columna, para realizarla podemos hacerlo de dos formas:

c=np.dot(a,b) #recibe las 2 matrices a multiplicar
d=a@b

La División

¿Por qué no tomamos la división?, pues por teoría la división de una matriz no es una operación que se realice como con los números reales, una división en una matriz se puede determinar a través de la multiplicación con la inversa de la matriz que estaría en el denominador, es decir:

A.B-1

Podemos obtener la inversa de una matriz a través del método inv, el cual pertenece a la subclase linalg, inv recibe como parámetro a la matriz  y devuelve su matriz inversa, entonces:

e=np.linalg.inv(b)

Ahora bien haciendo uso de las matrices de ejemplo a y b:

f=np.dot(a,np.linalg.inv(b))

DETERMINANTE DE UNA MATRIZ 

El determinante de una matriz podemos hallarlo haciendo uso del método det, que tiene como parámetro la matriz cuadrada que se desea hallar su determinante (no olvidar que el determinante siempre es un escalar y nunca un vector):

determinante=np.linalg.det(a)

MATRIZ TRANSPUESTA

La transpuesta de una matriz es la transformación en que las filas pasan a las columnas y viceversa, para realizar la transpuesta debemos de usar el método transpose, este es un método asociado directamente a la matriz, entonces:

g=a.transpose()

Por lo pronto es todo lo que podemos observar de las operaciones con matrices

 

Algoritmo de Regresión Lineal – Aprendizajes hacia la IA

La inteligencia artificial es algo que ya está en los comentarios académicos del día a día, está vinculada cuando se revisa el hacia donde van las tecnologías y  la rama específica a la que nos dedicamos, y es que definitivamente todo ha de estar involucrado en un futuro (esperemos que cercano) con la inteligencia artificial, yo, soy estudiante de ingeniería industrial, bien que mal entiendo por mis cursos de carrera que mi profesión básicamente se trata de tomar una buena decisión sobre qué hacer para responder a la gran interrogante de ¿Cuánto voy a ganar?, ese cuánto está determinado por las tomas de decisiones que se realicen para la consecución de maximizar dicho objetivo, una toma de decisión está influenciada por una infinidad de datos que nosotros delimitamos dada nuestras limitantes biológicas de análisis, pero imaginemos que tuviéramos un consejero que no tuviera esas limitantes de análisis y que nos pueda brindar información o conclusiones que realmente ponderen la evaluación entre una opción u otra o incluso agregar alguna desconocida en la toma de una decisión, esto es una de los principales objetivos de la inteligencia artificial, claro que tiene muchos más pero este es uno de ellos, y es el que se alinea mejor a mi profesión, supongo que tú al leer esto, tendrás que encontrar la significancia y el valor ponderado de la inteligencia artificial en tu carrera.

Ahora bien, mi especialidad en tópicos de programación se ha decantado por lo general hacia el desarrollo de plataformas web y proyectos varios, pero jamás sobre la inteligencia artificial, este el primer post de muchos en el cual intento crear mi propio True Path sobre la IA, esperando que con ello pueda ayudarte en construir tu propio camino.

REGRESIÓN LINEAL

Entonces el tema que nos atañe en esta publicación es la regresión lineal, un tópico que está demás decir en ingeniería siempre se aborda en distintos cursos que tengan que ver con la evaluación de datos como lo es la estadística. Para entender la regresión lineal vamos a realizarlo con un ejemplo; supongamos que nos tenemos que mudar (país, provincia, distrito sea cual sea el de tu comodidad), al mudarnos nosotros deseamos evaluar dónde mudarnos y empezamos la recolección de datos, decidimos que el costo de la habitación que hemos de usar está en función del área que la compone, hacemos una revisión y obtenemos los siguientes resultados.

regresion1

Este acto de definir la relación entre una variable independiente y dependiente para una representación de la realidad en función de relaciones se denomina modelar o establecer un modelo, cuando observamos nuestra gráfica, podemos intuir que podemos trazar una línea recta que atraviese la mayoría de puntos o que al menos esté lo más cercano a ellos.

regresion2

Y esto que acabamos de dibujar es un modelo, este modelo nos permite predecir que dado una cantidad de metros cuadrados de una habitación este tendrá un valor promedio correspondiente, ahora esto se le denomina un modelo de regresión lineal simple.

Nosotros sabemos que una recta se define como:

Y=w0+w1x

Tal que w0 representa al valor en la cual la recta corta al eje Y y w1 que es la pendiente, la pendiente que representa la inclinación de la recta es la relación que existe entre las variables de entrada X y las variables de salida Y; sin embargo, la realidad resulta ser mucho más compleja; el costo de una habitación no solo está restringido a las dimensiones en metros cuadrados, sino por ejemplo al X2=tipo de barrio en el que se encuentra, X3=cercanías a establecimientos y muchas otras variables más, cuando esto ocurre, y ocurre, nos encontramos ante un modelo de regresión lineal múltiple:

Y=w0+w1x1+w2x2+...+wnxn

En dicha circunstancias, ya no se trata de ubicar la recta para los datos bidimensionales sino del hiperplano que mejor se adapte a la nube de puntos, tal que cada una de las dimensiones representa uno de los atributos.

La forma más adecuada de representar nuestros resultados no es a través de ecuaciones como:

Y1=w0+w1x11+w2x12+w3x13  ...

Y2=w0+w1x21+w2x22+w3x23  ...

Y3=w0+w1x31+w2x32+w3x33  ...

.

.

Sino de forma matricial, donde cada columna es cada una de las características que hemos considerados (tipo de barrio, cercanías a establecimientos, etc.) es decir los datos de entrada y cada fila representa el valor de cada una de las mediciones, podemos llamar a dicha matriz como X, entonces:

regresion4

Así como existen los valores de entrada cada una de las mediciones de las filas de X, tendrá como salida un valor Y, el cual también podemos representar mediante una matriz:

regresion5

Esto ocurre de forma similar con los parámetros :

regresion6

Haciendo uso de la multiplicación de matrices, debemos de multiplicar la matriz X por la matriz transpuesta de W  y obtendremos la matriz Y:

Y=XWt

Lo cual al programarlos resultan más eficientes ya que el computador se entiende mejor con matrices, ahora bien para fines de comprensión vamos a restringir el post a una dimensión, entonces, Si tenemos que el costo de la habitación está relacionado directamente a la cantidad de metros cuadrados de la habitación, entonces nuestro modelo puede ser representado a través de una recta, pero ¿Cómo determinamos cuál es la mejor recta?, uno de los métodos que nos permite obtener la mejor recta es el método de los Mínimos Cuadrados,

MÉTODO DE MÍNIMOS CUADRADOS

entonces sea una función polinomial de la siguiente forma:

Y=a0+a1x+a2x2+...+anxn

Y que podemos representarla de la siguiente forma:

regresion7

Lo que vamos hallar son los residuos o también llamado el error, este residuo es la diferencia que existe entre los puntos de los datos obtenidos y el dato pronosticado por nuestra función, entonces podemos decir que en x1 el valor teórico es y(x1) y un valor experimental y1, y el residuo r1 será:

r1=y1-y(x1)

Podemos generalizar haciendo:

ri=yi-y(xi)

Entonces obtenemos el cuadrado:

regresion8

¿Por qué el cuadrado?, este residuo que estamos hallando podemos interpretarlo como el error del modelo en una variable de entrada (X), elevando al cuadrado estamos penalizando con mayor intensidad aquellos elementos que estén más alejados a nuestro modelo.

Ahora obtenemos la suma de todos los residuos, donde obtendremos un coste total del error, recordemos que el método busca obtener la combinación óptima de parámetros:

regresion9.png

Pero recordemos que y(xi) es la función polinomial, entonces reemplazando tenemos:

regresion10

Ahora nosotros estamos buscando la minimización del coste del error, sabemos que el valor mínimo de una función podemos hallarlo haciendo cero en su derivada, entonces hallemos la derivada de D respecto a cada uno de los parámetros, esta derivación es conocida como derivadas parciales.

regresion11.png

Donde igualamos a cero a cada uno de los resultados parciales:

regresion12

Entonces, volvamos a nuestro modelo de regresión lineal simple  en el que nuestro modelo está representado por la recta:

Y(xi)=w0+w1xi

Y si los datos experimentales son Yobtenemos la sumatoria de residuos:

regresion13

Desarrollamos D y obtenemos:

regresion15

Sabemos que la solución se encontrará en una cantidad n+1 de ecuaciones, de donde obtenemos las derivadas parciales:

regresion14

regresion16

De donde despejamos y obtenemos tal que N sea el total de elementos (ayudas de sumatorias):

regresion17

Ahora despejando w de la primera ecuación en función  de wy reemplazando en la segunda ecuación obtenemos el valor de w1, luego ese valor reemplazamos en la primera ecuación y nos queda:

regresion18

 

Y bueno, esto es lo que tenemos por el momento, cuando tenemos datos reales, podemos incluso someterlo a una matriz y hacer su resolución de forma más sencilla, pues imagina que fueran muchos más atributos.

Arduino Ethernet Shield y Python – parte 2

Anteriormente

PROGRAMACIÓN EN PYTHON

En esta ocasión abordaremos el caso desde python, para así completar todo lo necesario de nuestro proyecto, recordemos que queremos obtener la data desde arduino a través del protocolo udp haciendo uso del ethernet shield y  el arduino, para ello en la primera etapa hemos implementado el servidor que despacha los datos sobre la temperatura.

esquema_general2

Bien, ahora lo que debemos hacer es implementar desde el lado de python el recibir esa información y esto lo vamos a realizar a través del uso de los sockets, un socket es una comunicación bidireccional que se establece en los procesos y estos procesos pueden ser en una misma máquina o en diferentes, así también pueden desplegarse en diferentes protocolos siendo los más emblemáticos tcp/ip y udp. Python nos brinda un módulo para la implementación de sockets, este módulo es socket (muchas vueltas al asunto ¿no?).

Para ello creamos un fichero python con el nombre que quieras en nuestro IDE favorito ( yo sé que quieres decir que es VSCODE), hecho esto importamos el módulo socket y el módulo time con el fin de realizar pausas en el pedido de datos:

from socket import *
import time

Ahora debemos de crear una instancia de socket, esto lo podemos hacer con la función socket() el cual recibe dos parámetros, el primer parámetro hace referencia a la familia de direcciones y el segundo hace referencia al protocolo específico a usar, en nuestro caso hemos de usar IPv4 y UDP respectivamente:

cliente_socket=socket(AF_INET,SOCK_DGRAM)

En una tupla agregamos tanto el ip como el puerto por el cual nos comunicamos al servidor, recordemos que es: 192.168.1.48 y 3000 respectivamente:

direccion=("192.168.1.48",3000)

Agregamos un segundo de espera en alguna respuesta de parte del arduino a través del método settimeout():

cliente_socket.settimeout(1)

Creamos un bucle infinito de solicitudes al servidor, esto lo haremos a través de un while, dentro de el  debemos de enviar la solicitud, para ello definimos un string llamado data y le asignamos un valor de 1 (recuerda que puede ser el valor que se te antoje), luego de ello debemos enviarlo con el método sendto, este método recibe como parámetros la data de tipo byte y  la dirección que es una tupla, pero nuestra data es un string, entonces debemos de realizar una casteo a byte y eso se realiza con el método encode quedando de la siguiente forma:

cliente_socket.sendto(data.encode(),direccion)

Cuando hemos enviado la información deberíamos poder recibir algún tipo de dato, aunque esto no siempre se cumple, es por ello que debemos de hacer la lectura de los datos recibidos por el servidor a través de una estructura try/except, dentro del try/except  recibimos el dato a través del método recvfrom() que recibe como parámetro el tamaño del buffer, este método nos devuelve un par de elementos que son tanto la data en bytes y la dirección de donde provino, esencialmente estamos interesados en la data que es la temperatura (rec_data), recibido ello podemos hacer un casteo y pasarlo a float y finalmente la impresión del dato, quedando de la siguiente forma:

try:

   rec_data,addr=cliente_socket.recvfrom(2048) 

   temperatura=float(rec_data) 

   print("La temperatura es: {0}".format(temperatura))

except:

   print("Algo anda mal")

En el except hemos colocado información de que algo anda mal, podríamos sí capturar el error que se produce y hacerle un seguimiento; sin embargo, para fines del ejemplo no lo haremos, finalmente hacemos que nuestro, agregamos un descanso para la solicitud de 2 segundos:

time.sleep(2)
Y finalmente todo el código del fichero queda de la siguiente forma:
from socket import *

import time

direccion=("192.168.1.48",3000)

cliente_socket=socket(AF_INET,SOCK_DGRAM)

cliente_socket.settimeout(1)

while(1):

   data="1" #la data que enviaremos

   cliente_socket.sendto(data.encode(),direccion) 

   try:

      rec_data,addr=cliente_socket.recvfrom(2048) 

      temperatura=float(rec_data) 

      print("La temperatura es: {0}".format(temperatura))

   except:

      print("Algo anda mal")

   time.sleep(2)
Ahora aquí una imagen de la data recibida, recordar que la data recibida deben de traducirla a la medida en centígrados en la que se encuentren:

 

datafinal

Arduino Ethernet Shield y Python – parte 1

En anteriores post he mencionado el espacio que Python se ha ido ganando a pulso en el mundo de la ingeniería y las matemáticas, ya sea por su sencillez de sintaxis donde más se preocupa en el fondo que en la forma, es así que si pretendes realizar algún tipo de  aplicación relacionada a la ingeniería, python es tu lenguaje ideal (siempre y cuando la aplicación sea para lenguajes de alto nivel). Paralelo a ello vemos el avance de las aplicaciones en IoT que es uno de los corolarios de la tercera revolución industrial y que está recibiendo un gran empuje, entusiastas (como yo) intentamos de entender y comprender el paradigma de su funcionamiento y de sus posibilidades, y como se dice en algunas películas de peleas:

Los hombres se entienden en la pelea

Pues los desarrolladores se entienden cuando desarrollan, entonces que nos trae en esta ocasión, algo relativamente simple, vamos a recibir datos desde un arduino a través de la red de internet y estos datos seamos capaces de enviarlo a una web o al menos poderlos sensar,  simple el enunciarlo pero que guarda cierta complejidad en su implementación; para ello necesitamos de:

  • Arduino UNO.
  • Placa Ethernet para Arduino.
  • La base shield de Arduino Grove (esto es opcional, pues podrías usar un protoboard y cables hembra – macho).
  • IDE de programación (Arduino IDE y VSCODE), también son opcionales, ya que al ser Ethernet una librería oficial de Arduino también se encuentra disponible en Arduino Web Editor y  en lugar de VSCODE puedes usar  un IDE más especializado como PyCharm.
  • Un sensor cualquiera (entre más simple mejor) de temperatura, pero verás, cualquier sensor será útil.

montaje

EL DILEMA DEL PROTOCOLO

¿Qué protocolo usar? de los muchos que existen y que se especializan en estas lides, podemos quizá para este experimento establecer una discusión entre  TCP y UDP, el primero ampliamente usado por todos pues es el que da soporte a las principales aplicaciones de internet como HTTP, FTP, SSH y SMTP, y el segundo empleado para transmisiones de audio o vídeo, leamos un poco de su funcionamiento:

Primero TCP

Las aplicaciones envían flujos de bytes a la capa TCP para ser enviados a la red. TCP divide el flujo de bytes llegado de la aplicación en segmentos de tamaño apropiado (normalmente esta limitación viene impuesta por la unidad máxima de transferencia (MTU) del nivel de enlace de datos de la red a la que la entidad está asociada) y le añade sus cabeceras. Entonces, TCP pasa el segmento resultante a la capa IP, donde a través de la red, llega a la capa TCP de la entidad destino. TCP comprueba que ningún segmento se ha perdido dando a cada uno un número de secuencia, que es también usado para asegurarse de que los paquetes han llegado a la entidad destino en el orden correcto. TCP devuelve un reconocimiento por bytes que han sido recibidos correctamente; un temporizador en la entidad origen del envío causará un timeout si el asentimiento no es recibido en un tiempo razonable, y el (presuntamente desaparecido) paquete será entonces retransmitido. TCP revisa que no haya bytes dañados durante el envío usando un checksum; es calculado por el emisor en cada paquete antes de ser enviado, y comprobado por el receptor“, Fuente.

Ahora vamos con UDP:

User Datagram Protocol (UDP) es un protocolo del nivel de transporte basado en el intercambio de datagramas. Permite el envío de datagramas a través de la red sin que se haya establecido previamente una conexión, ya que el propio datagrama incorpora suficiente información de direccionamiento en su cabecera. Tampoco tiene confirmación ni control de flujo, por lo que los paquetes pueden adelantarse unos a otros; y tampoco se sabe si ha llegado correctamente, ya que no hay confirmación de entrega o recepción. Su uso principal es para protocolos como DHCP, BOOTP, DNS y demás protocolos en los que el intercambio de paquetes de la conexión/desconexión son mayores, o no son rentables con respecto a la información transmitida, así como para la transmisión de audio y vídeo en tiempo real, donde no es posible realizar retransmisiones por los estrictos requisitos de retardo que se tiene en estos casos“, fuente: la misma de la anterior.

Comparativa TCP y UDP

UDP

Proporciona un nivel de transporte no fiable de datagramas, ya que apenas añade la información necesaria para la comunicación extremo a extremo al paquete que envía al nivel inferior. Lo utilizan aplicaciones como NFS (Network File System) y RCP (comando para copiar ficheros entre ordenadores remotos), pero sobre todo se emplea en tareas de control y en la transmisión de audio y vídeo a través de una red. No introduce retardos para establecer una conexión, no mantiene estado de conexión alguno y no realiza seguimiento de estos parámetros. Así, un servidor dedicado a una aplicación particular puede soportar más clientes activos cuando la aplicación corre sobre UDP en lugar de sobre TCP.

TCP

Es el protocolo que proporciona un transporte fiable de flujo de bits entre aplicaciones. Está pensado para poder enviar grandes cantidades de información de forma fiable, liberando al programador de la dificultad de gestionar la fiabilidad de la conexión (retransmisiones, pérdida de paquetes, orden en el que llegan los paquetes, duplicados de paquetes…) que gestiona el propio protocolo. Pero la complejidad de la gestión de la fiabilidad tiene un coste en eficiencia, ya que para llevar a cabo las gestiones anteriores se tiene que añadir bastante información a los paquetes que enviar. Debido a que los paquetes para enviar tienen un tamaño máximo, cuanta más información añada el protocolo para su gestión, menos información que proviene de la aplicación podrá contener ese paquete (el segmento TCP tiene una sobrecarga de 20 bytes en cada segmento, mientras que UDP solo añade 8 bytes).

Por eso, cuando es más importante la velocidad que la fiabilidad, se utiliza UDP. En cambio, TCP asegura la recepción en destino de la información para transmitir, fuente.

Finalmente podemos hacer esta analogía con algo de humor:

duv11av99nm11

POR CUAL NOS DECANTAMOS

Actualmente existe una total controversia en el aspecto de la seguridad en el IoT (otro ejemplo), es cierto que los protocolos actuales o el como se implementan les falta aún camino para recorrer en términos de seguridad; sin embargo, ello no puede limitar acercarnos a entender el paradigma de lo que significa el IoT, así que haciendo dicha observación de obviar en sentido estricto la seguridad, estableceremos el esquema de lo que deseamos implementar.

esquema_general

Cuando nuestro Arduino ha de enviar datos de lo que ha censado, estos datos son muy grandes, y por lo general no muy diferenciados (esto es porque la diferenciación se da con la variable tiempo), imaginemos pues que estamos censando la temperatura a lo largo del día, decir que a una hora específica se está a determinada temperatura es por la resultante de la media (teniendo en cuenta la problemática de los valores extremos); podemos decir que no nos importa la secuencia de los datos ni necesitamos asegurarnos de que todos los datos lleguen, sino la gran mayoría de datos independientemente de su secuencia, entonces queda casi por obviedad que apostaremos por el protocolo UDP que se adapta mejor a nuestro requerimiento.

Decidido el protocolo ahora pasemos a la parte de implementación, la implementación tiene dos componentes en el campo del desarrollo; el primero será la programación de nuestra placa arduino montada conjuntamente con la placa ethernet, y el segundo del lado del computador (servidor más adelante) que se encargará de recepcionar los datos y de ahí si es posible enviarlos a una web o leerlos en una consola.

PROGRAMACIÓN EN ARDUINO

Intentemos bosquejar todo lo que hemos de realizar:

df.png

Entonces, abrimos el Arduino IDE y creamos un nuevo proyecto. Lo que necesitamos es incluir las librerías necesarias:

#include <Ethernet.h> //librería de ethernet
#include <EthernetUdp.h> //librería de UDP
#include <SPI.h>

La librería Ethernet es la utilizada para el manejo de la placa ethernet y la conexión a internet,  Ethernet.Udp   es el encargado de manejar el protocolo UDP y SPI es la librería que se encarga de la gestión de transmisión de datos entre los circuitos, la placa Ethernet en su misma descripción hace referencia a que la comunicación se realiza a través del bus SPI en los pines 11, 12 y 13 (si quieres saber más).

Ya que hemos de registrar la temperatura del ambiente, debemos de crear una variable de tipo entera y el pin donde se ha de conectar el sensor y una variable donde se almacenará los valores que se obtengan del medio, entonces:

int sensor_pin=A0;
double valor_sensor=0

Para que nuestro Arduino pueda conectarse a internet debe poder ser identificado y que se le asigne una IP, la identificación se realiza a través de la dirección MAC y es un arreglo de tipo byte:

byte mac[]={0xDE,0xAD,0xBE,0xEF,0xFE,0xEE};

El ip le asignamos a través de la clase IPAddress, este ip que le asignamos debe tener la misma configuración que el que tiene nuestra red de internet, de esa configuración debemos de coger uno tal que no lo esté usando otro dispositivo que esté conectado a nuestra red para que no existan conflictos, ¿Cómo saber la configuración de nuestra IP?, en windows es a través del CMD y digitando la instrucción ipconfig.

IPAddress ip(192,168,1,48);

Cuando enviamos un dato a través de internet nosotros debemos de indicar tanto el ip de destino como el puerto, el puerto definitivamente es un valor positivo y para no cometer errores vamos a restringirlo a ello usando la palabra reservada unsigned:

unsigned int puerto=3000;

CUESTIÓN OPCIONAL

Supongamos que tengamos cuando hagamos un requerimiento de datos este sea específico, es decir, sea que tengamos temperatura y presión, el cliente debe tener la facultad de elegir que tipo de dato desea observar, es así que debemos de almacenar dichos solicitudes en un arreglo y esto lo podemos hacer con lo que se describe a continuación.

Cuando Arduino tenga que leer los paquetes de datos y que esté sobre UDP debe ser almacenado en un arreglo de caracteres, la librería ethernet define una constante denominada UDP_TX_PACKET_MAX_SIZE equivalente a 24 y que asignaremos como tamaño del arreglo, entonces (más aquí):

char paqueteBuffer[UDP_TX_PACKET_MAX_SIZE];

Recordemos que la data que hemos recibido es de tipo Char, entonces necesitamos convertirlo a una cadena a través de un String, para ello creamos una variable que ha de recepcionar esta conversión ya que las cadenas son más sencillas de trabajar que un char o un arreglo:

String dataReq;

Así también debemos de crear la dimensión de ese paquete que se está leyendo:

int paquete_size;

FIN DE LA CUESTIÓN OPCIONAL

Finalmente creamos una instancia de ethernet UDP:

EthernetUDP udp;

Hechos todos los preparativos debemos de proceder a inicializar en la función setup.

#include <Ethernet.h> //librería de ethernet
#include <EthernetUdp.h> //librería udp
#include <SPI.h>

int sensor_pin=A0;
double valor_sensor=0;

byte mac[]={0xDE,0xAD,0xBE,0xEF,0xFE,0xEE}; //dirección MAC
IPAddress ip(192,168,1,48);//ip que asignamos a nuestro arduino
unsigned int puerto=3000;//puerto que se le asigna
char paqueteBuffer[UDP_TX_PACKET_MAX_SIZE];//dimension del char 
String dataReq; //cadena para nuestro string
int paquete_size;//tamaño del paquete
EthernetUDP udp; //creamos un objeto UDP

Función Setup

Debemos de iniciar el puerto serial:

Serial.begin(9600);

Ahora debemos de inicializar la librería con las configuraciones que hemos asignado con el método begin de la clase Ethernet, ahora este método tiene sobrecarga, tal como sabemos los métodos solo se diferencian por la cantidad de parámetros cuando son sobre escritos, nosotros solo poseemos o hemos configurado una ip (el creado a través de la clase IPAddress y una dirección mac (que la tenemos en el arreglo de tipo byte), entonces usaremos el método que solo requiera de ellos:

Ethernet.begin(mac,ip);

De igual forma y con el mismo fin sucede con la librería UDP;sin embargo, esta librería requiere que como parámetro el paso de un puerto, que es el puerto por el cual se ha de comunicar  (recordar que hemos asignado un puerto, el puerto 3000:

udp.begin(puerto);

Establecemos una pausa  de 1.5 segundos en su equivalente 1500 mili segundos con el método delay:

delay(1500);

Con todo ello todos los elementos que necesitamos ya se encuentran inicializados, ahora debemos de observar que es lo que sucede en la siguiente función:

void setup() {
   Serial.begin(9600);
  Ethernet.begin(mac,ip);//inicializa el ethernet
  udp.begin(puerto);//inicializar el udp
  delay(1500);
}

Function Loop

¿Cómo ha de funcionar nuestra comunicación?, bueno nosotros debemos de enviar datos siempre que existan datos, es decir, debemos de asegurar que se hayan recibido correctamente los datos del sensor para que puedan ser enviados, la forma en la que se capturan estos datos es a través del método analogRead() el cual recibe como parámetro el pin donde se encuentra conectado el sensor:

 valor_sensor=analogRead(sensor_pin);

Muy bien, recordemos que nuestra máquina donde configuraremos nuestro fichero en python estará fungiendo como un cliente que solicitará constantemente los datos que arduino tenga en relación a la temperatura, esto se hará a través del protocolo UDP, entonces nosotros debemos de estar atentos a la solicitud de datos y cuando este se reciba proceder al envío de los mismos, para ello solo debemos hacer la siguiente inferencia: “Si el paquete de datos es mayor que cero, entonces existe una solicitud“, el paquete de datos en UDP se realiza a través del método parsePacket():

if(udp.parsePacket()>0){}

Ahora sabemos que existe una solicitud; sin embargo, no creo que sea tan apropiado leer que es lo que se está pidiendo, ya que lo único que haremos es despachar temperatura, es por ello que solo aseguramos que exista una solicitud sin saber que es lo que solicita porque lo único que obtendrá será temperatura, ¿En qué situación si nos interesa saber el contenido?, cuando por ejemplo tengamos más de un sensor, si te encuentras en dicha situación deberías primero leer el paquete con:

 udp.read(paqueteBuffer,UDP_TX_PACKET_MAX_SIZE);
 String datReq(paqueteBuffer); //convierte el array en un string

Y luego de ello reemplazar:

if(udp.parsePacket()>0){}

por:

if(datReq=="Condición 1"){}

Tal que condición 1 sea quizás la solicitud de temperatura o presión

Sin embargo, para este ejemplo no nos decantamos por la lectura, sino que nos basta con saber que existe una solicitud, bueno, entonces debemos de enviar este dato a través del protocolo udp y esto lo hacemos a través del método beginPacket(), este método recibe como parámetros tanto el ip como el puerto al que vamos a conectarnos, el ip y el puerto lo obtenemos dado que un cliente ya estableció un requerimiento a través de los métodos remoteIP() y remotePort(), entonces dentro del if:

udp.beginPacket(udp.remoteIP(),udp.remotePort());

Hecho eso, capturamos la data del sensor y la enviamos, esto lo hacemos con el método print() de la clase Server que tiene como parámetro la data que se desea enviar, en nuestro caso deseamos que lo que se envíe sea el valor del sensor, se debe acotar que cuando se envía una cadena, un número o lo que fuera este será enviado como un conjunto de caracteres (Si se envía 123 este será ‘1’ ‘2’ ‘3’.

udp.print(valor_sensor);

Ahora debemos de finalizar la escritura de los datos a través del método endPacket():

udp.endPacket();

Queda de la siguiente forma:

void loop() {
   valor_sensor=analogRead(sensor_pin);
   if(udp.parsePacket()>0){
        udp.beginPacket(udp.remoteIP(),udp.remotePort());
        udp.print(valor_sensor);
        udp.endPacket();

   }
}

Bien, hasta el momento hemos asegurado que nuestro servidor esté en funcionamiento, esto lo podemos comprobar haciendo un pin al ip asignado a nuestro arduino, el resultado debería ser el siguiente:

ping.png

Desarrollo de una Aplicación en Python para resolución de Problemas de Programación Lineal – caso Transportes parte 2

Anteriormente:

Hola, en esta ocasión volvemos a la resolución de problemas bajo la programación lineal a través de python, pero en este post trataremos de un caso específico que son los problemas de transportes. El problema del transporte es un problema de redes especial que se  basa en el esquema  de que existe la necesidad de llevar unidades de un punto específico llamados fuente u origen hacia otro punto específico llamado destino tal que existe un costo de envío de la fuente hacia el destino , el objetivo principal de un modelo de transporte es la satisfacción de todos los requerimientos establecidos por los destinos con el menor costo posible. Un problema de transporte tiene un contexto de aplicación muy amplio como inventarios o asignación de elementos; este tipo de problemas (su estructura) permite la creación de múltiples alternativas de solución como estructuras de asignación o métodos heurísticos como Vogel, Esquina Noroeste o Mínimos Costos. Un problema de transporte se puede representar de la siguiente forma:

transporte1

Supuestos

  • Haz leído al menos la parte 1.
  • No te es necesario leer la parte 1 ya que tienes conocimientos en python y la resolución de problemas de programación lineal.
  • Nuestro caso abordará la situación más básica, es decir: la sumatoria de cantidades disponibles del origen es igual a la sumatoria de requerimientos de los destinos.

Empecemos

Enunciado

Tenemos que enviar cajas de cervezas de dos cervecerías a cinco bares de acuerdo al siguiente gráfico:

Trans_problem

Así también el área de finanzas nos informa que el costo de transporte por caja de cada origen hacia cada destino es:

t1

Se nos pide hallar el costo mínimo que satisfaga todas las necesidades de los bares.

Modelado

Sabemos que un problema de programación lineal tiene:

  • Variables de decisión
  • Una función objetivo
  • Un conjunto de restricciones de no negatividad de las variables de decisión

Nuestras variables de decisión podemos hacerlas de la siguiente forma:

Xij=la cantidad enviada desde el origen i al destino j

Sea Cij el costo de envío desde el origen i al destino j y sea Xij lo representamos de la siguiente forma:

transporte

Podemos decir entonces que la función objetivo se puede representar de la siguiente forma:

t3

Entonces de forma general si tuviéramos “n” orígenes y “m” destinos:

t2

Las restricciones del problema se basan en lo siguiente, la suma total de cantidades enviadas del origen “i” a los destinos “j” no pueden exceder la cantidad máxima del mismo origen “i“, entonces sea ai la cantidad máxima que puede despachar una cervecería:

t4

Así también las cantidades recibidas desde los orígenes “i” no pueden exceder la cantidad máxima requerida del destino “j“, entonces sea bj la cantidad requerida por un bar:

t5

Modelado lo llevamos a Python

Recuerda que el detalle de cada uno de los métodos e instancias de clase se encuentra en la parte 1

Creamos un fichero, abrimos la consola (en mi caso la consola que viene en Visual Studio Code) e instalamos pulp:

pip install pulp

En el fichero debemos de importar la librería (se estila la importación en la parte superior del fichero):

from pulp import *

Creamos una instancia de la clase LpProblem :

problema=pulp.LpProblem("Problema de Distribución de Cervezas",pulp.LpMinimize)

Ahora debemos de generar las estructuras necesarias para solucionar nuestro problema, para ello vamos a crear una lista con las etiquetas de los orígenes y un diccionario, primero vamos con la oferta máxima de cada una de los orígenes:

cervecerías=["Cervecería A","Cervecería B"]
oferta={"Cervecería A":1000,"Cervecería B":4000}

Recuerda que tanto los elementos de la lista y la clave del diccionario que estamos creando deben ser idénticos, ya que cuando se realicen las iteraciones y hubiese algún valor diferente podríamos sufrir de datos perdidos y todo se echaría perder. El procedimiento anterior lo realizamos con la demanda de cada uno de los bares:

bares=["Bar 1","Bar 2","Bar 3","Bar 4","Bar 5"]
demanda={"Bar 1":500,"Bar 2":900,"Bar 3":1800,"Bar 4":200,"Bar 5":700}

Por último creamos una lista de los costos, esta lista será una lista de listas, donde cada lista que es elemento será los costos asociados de llevar del origen i al destino j:

costos=[[2,4,5,2,1],[3,1,3,2,3]]

Ahora debemos de crear un diccionario en que se asocien origen destino y tenga como elementos los costos correspondientes tal como la matriz del enunciado:

t1

Entonces hablamos de que cada valor está asociado a un destino y un destino a un origen y que se exprese a través de un diccionario que en nuestro caso es de 2 dimensiones (ver más ejemplos), operativamente el resultado que esperamos es el siguiente:

costos={
         "Cervecería A":{"Bar 1":2,"Bar 2":4,"Bar 3":5,"Bar 4":2,"Bar 5":1},
         "Cervecería B":{"Bar 1":3,"Bar 2":1,"Bar 3":3,"Bar 4":2,"Bar 5":3}
        }

Para ello pulp tiene una función que es makeDict():

makeDict(headers,array,default=None)

Los headers son una lista que contiene como elementos las listas de las etiquetas, array es la lista de valores expresado en las dimensiones que tendrá la matriz resultante (2×5 en nuestro caso), entonces generamos el diccionario bidimensional costos:

costos=pulp.makeDict([cervecerias,bares],costos,0)

Ahora necesitamos crear una lista con todas las rutas posibles y que se expresen como pares ordenados, esto lo podemos hacer con las tuplas, el resultado esperado es:

[("Cervecería A","Bar 1"),("Cervecería A","Bar 2")... ("Cervecería B",Bar 5")]

Para ello vamos a crear una lista por comprensión, y que asignaremos a una variable rutas:

rutas=[(c,b) for c in cervecerias for b in bares]

Al igual que tenemos una matriz de costos entre el origen y el destino debemos de crear las variables de las cantidades:

t6

Como se tratan de variables debemos de usar la clase LpVariable y su método dicts,

dicts(nombre_diccionario,indexs_tuplas,límite_inferior=None,Límite_superior=None,cat=categoria_Integer_Continuous

Aquí debemos de hacer una observación, el constructor del método admite una lista o una tupla de listas dentro del parámetro indexs, la cantidad de elementos en la tupla determina las dimensiones de la matriz, así también el límite inferior es cero por la restricción de no negatividad; sin embargo, el límite superior no está acotado y por ello usaremos None, finalmente tratamos con elementos enteros por lo que su categoría es pulp.LpInteger, entonces:

cantidad_variable=pulp.LpVariable.dicts("cantidadxruta",(cervecerias,bares),0,None,pulp.LpInteger

El resultado esperado debe ser:

cantidad_variables={
              'Cervecería A': {'Bar 1': cantidadxruta_Cervecería_A_Bar_1, 'Bar 2': cantidadxruta_Cervecería_A_Bar_2, 'Bar 3': cantidadxruta_Cervecería_A_Bar_3, 'Bar 4': cantidadxruta_Cervecería_A_Bar_4, 'Bar 5': cantidadxruta_Cervecería_A_Bar_5}, 
              'Cervecería B': {'Bar 1': cantidadxruta_B_Bar_1, 'Bar 2': cantidadxruta_Cervecería_B_Bar_2, 'Bar 3': cantidadxruta_Cervecería_B_Bar_3, 'Bar 4': cantidadxruta_Cervecería_B_Bar_4, 'Bar 5': cantidadxruta_Cervecería_B_Bar_5}
                   }

Con todo esto ya tenemos listo todas las estructuras necesarias para la resolución del problema, entonces lo primero es introducir la función objetivo, tal como observamos la función objetivo se expresa como:

t2

Y debemos de expresarlo de la siguiente forma:

problema+=lpSum(costos[c][b]*[cantidad_variables[c][b] for (c,b) in rutas),"Función Objetivo"

recordemos que problema es una instancia de LpProblem, el cual es el encargado de almacenar la función objetivo y las restricciones, esto lo logramos a través de lpSum, ahora vamos a entender que es lo que hemos hecho, supongamos que estamos buscando la siguiente expresión:

t7

Que significa: “el costo de la cervecería A hacia el Bar 1 multiplicado por la cantidad enviada desde la cervecería A hacia el Bar 1“, esta expresión la tenemos configurada en los diccionarios de costos y cantidad_variables respectivamente y como necesitamos discriminar por ruta, esta se encuentra almacenada en el par ordenado (tupla) de la lista rutas.

Explicado a ello ahora procedemos de forma similar con las restricciones y que también tenemos que agregar a nuestra instancia problema:

Primero abordaremos las restricciones de la oferta, para la oferta tenemos lo siguiente:

t4

Y lo expresamos de la siguiente forma:

for c in cervecerias:
  problema+=lpSum([cantidad_variables[c][b] for b in bares])<=oferta[c],"Restricciones de oferta"

La cantidad de ecuaciones  para la restricción de la oferta es la cantidad de elementos que existen en la lista de cervecerías, así que:

c=Ai tal que i=1,2,3 … n

Queremos obtener:

t8_oferta

  • X11=cantidad enviada de la cervecería A hacia el bar 1
  • X21=cantidad enviada de la cervecería B hacia el bar 1

El que se encuentra en constante iteración son los bares y que dicha suma no puede exceder al valor de la cervecería origen.

De similar forma ocurre con la demanda:

t5

for b in bares:
  problema+=lpSum([cantidad_variables[c][b] for c in cervecerias])<=demanda[b],"Restricciones de demanda

En la demanda la suma de envíos desde las cervecerías no puede exceder a la demanda solicitada por cada bar.

 

Hecho todo esto ahora podemos resolver el problema, para ello ya de casi forma automática (se encuentra explicado de forma detallada en la parte 1), debemos de escribir el archivo Lp:

problema.writeLp("ProblemaTransporte.lp")

Lo resolvemos:

problema.solve()

Imprimimos el estado del problema, para saber si pudo ser resuelto o no:

print("Status: {}".format(pulp.LpStatus[problema.status]))

Luego imprimimos los resultados por variable para conocer el detalle del estado:

for v in problema.variables():
  print("{0:}={1:}".format(v.name,v.varValue))

Finalmente imprimimos el resultado del costo optimizado:

print("Costo optimizado = {}".format(problema.objective.value()))

t9_resultado

No te preocupes si el nombre de la variable es distinto:

ruta_Cervecería_A_Bar_1==cantidadxruta_Cervecería_A_Bar_1

En un siguiente post vamos a crear una interfaz que nos permite colocar n orígenes y m destinos, así nuestra aplicación estará apta para cualquier tipo de problema donde existe un origen y un destino, saludos.