← Back to list

Cómo correr fácilmente solvers PDDL en tu propia infraestructura

If you are learning, practicing, implementing classic planning, is almost sure you’ll need to use solvers to do that. Commonly you’ll use…

Ramón Barrios Láscar · 2024-01-27 15:39 · 1 claps · 10.4 min read
#pddl #classical-planning #julia #jupyterlab
Open on Medium ↗
Wiki topics: EDU · Education & Learning

Cómo correr fácilmente solvers PDDL en tu propia infraestructura

Nota: esta entrada se puede encontrar en inglés en https://medium.com/@ramonbarrioslascar/how-to-easily-run-pddl-solvers-in-your-own-infrastructure-29daeffddcc8

Si está aprendiendo, practicando, o implementando planificación clásica, es casi seguro que necesitará usar solvers para hacerlo. Es común usar algunos recursos en línea (como planning.domains), pero, así dependerá de la infraestructura ajena y su disponibilidad, y si uno no paga por los recursos no puede depender de esos recursos.

Usa Julia

Tan simple como eso. Si usas el lenguaje de programación Julia podrás correr localmente solvers, podrás escoger entre diferentes solvers, con muchas funciones heurísticas variadas y entonces realmente optimizar los planes. ¿Cómo se puede lograr todo esto de una manera fácil? La respuesta fácil es usar docker (u otro software de contenedores) con la variante de Julia de JupyterLab, y así tener fácilmente acceso continuo a tus solvers sobre tus propios dominios y problemas escritos en PDDL.

Un contenedor para correr la versión con Julia de JupyterLab

Puedes obtener la versión de JupyterLab para Julia descargando la imagen jupyter/julia-notebook en Docker (https://hub.docker.com/r/jupyter/julia-notebook) o mejor, en Quay (https://hub.docker.com/r/jupyter/julia-notebook).

Figure 1. Jupyter Julia Notebook page on Docker.io

Figure 1. Jupyter Julia Notebook page on Docker.io

Figure 2. Jupyter Julia Notebook page on Quay.io

Figure 2. Jupyter Julia Notebook page on Quay.io

Solo usando una de esas imágenes es muy poco lo adicional que necesitarás. Es claro que necesitarás una manera de ejecutar los contenedores, entonces asumo que si sigues leyendo esto es porque tienes la manera.

Si estás inmerso en big data, AI, ML, DL, DS, eres analista, ingeniero o científico y tienes la manera de correr contenedores estaré como 90 % seguro de que tienes al menos un contenedor de notebooks de Jupyter con Python y asumiré que querrás la posibilidad de correr ambos contenedores al mismo tiempo, entonces mostraré la manera de cambiar las propiedades para correr ambos contenedores sin conflictos.

Crear un nuevo contenedor

Asumiré que ya tienes un contenedor en el puerto TCP predeterminado 8888 y que entonces necesitamos cambiarnos de eso. Debido a que no sé qué clase de interfaz GUI o web tienes, crearé un contenedor usando la sintaxis básica de docker:

[superuser@Hermes ~]$ docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED        STATUS                PORTS                                                                                                                                                                                                   NAMES
0561512268d9   rbl/jupyter-spark-py-jl-r   "tini -g -- start-no…"   3 weeks ago    Up 9 days (healthy)   0.0.0.0:4040->4040/tcp, 0.0.0.0:8888->8888/tcp                                                                                                                                                          jupyter
a22ff2761a42   cassandra                   "docker-entrypoint.s…"   6 weeks ago    Up 9 days             0.0.0.0:32772->7000/tcp, 0.0.0.0:32771->7001/tcp, 0.0.0.0:32770->7199/tcp, 0.0.0.0:32769->9042/tcp, 0.0.0.0:32768->9160/tcp                                                                             cassandra
8f0446c3557e   linuxserver/plex:latest     "/init"                  7 months ago   Up 3 weeks            0.0.0.0:3005->3005/tcp, 0.0.0.0:8324->8324/tcp, 0.0.0.0:32410->32410/udp, 0.0.0.0:32400->32400/tcp, 1900/udp, 0.0.0.0:32469->32469/tcp, 0.0.0.0:32412-32414->32412-32414/udp, 0.0.0.0:15353->5353/udp   plex
fa74a19d31ee   andyshinn/dnsmasq:latest    "dnsmasq -k"             2 years ago    Up 8 days                                                                                                                                                                                                                     dnsmasq
[superuser@Hermes ~]$

El comando de arriba muestra una pequeña infraestructura sin un contenedor de Julia corriendo, pero sí podemos ver los puertos TCP en uso. Nótese que en esta infraestructura queremos acceder a notebooks anteriores y a los datos que ya tenemos, entonces le daremos una "atadura" (bind) de volumen local al contenedor. Esto último es muy útil, porque es la manera más fácil de compartir archivos con el contenedor.

[superuser@Hermes ~]$ ls -l /share/Container/Jupyter/
total 24332
-rw-rw---- 1 superuser everyone   34923 2023-12-30 18:14 actividad_1.ipynb
-rw-rw---- 1 superuser everyone   13492 2023-12-16 17:30 Actividad_3.ipynb
-rw-rw---- 1 superuser everyone    8057 2023-12-16 17:14 Actividad_3.py
-rw-rw---- 1 superuser everyone  112248 2024-01-10 13:40 after.png
drwxrwx--- 3 superuser everyone    4096 2024-01-17 11:49 AID/
-rw-rw---- 1 superuser everyone  796824 2024-01-17 20:57 AID_Actividad_2.ipynb
-rw-rw---- 1 superuser everyone  407224 2023-12-01 10:33 AID_Entrega1.ipynb
-rw-rw---- 1 superuser everyone   38572 2023-12-30 18:20 Barrios_Lascar_Ramon_actividad_1.ipynb
-rw-rw---- 1 superuser everyone  869960 2024-01-17 15:05 Barrios_Lascar_Ramon_actividad_2.ipynb
drwxrwx--- 4 superuser everyone    4096 2023-12-05 16:28 BDDB/
drwxrwx--- 3 superuser everyone    4096 2024-01-03 13:00 BDPE/
-rw-rw---- 1 superuser everyone   56461 2024-01-10 13:22 before.png
... recortado ...
drwxrwx--- 3 superuser everyone    4096 2023-12-02 16:24 kaggle/
drwxrwx--- 2 superuser everyone    4096 2024-01-14 12:35 kaggle_house_prices/
-rw-rw---- 1 superuser everyone   11277 2023-11-29 12:27 kaggle-market.ipynb
-rw-rw---- 1 superuser everyone 1520483 2024-01-14 20:39 kaggle_predict_house_prices_ml.ipynb
-rw-rw---- 1 superuser everyone     508 2023-12-06 09:12 LPAR_data.2023-12-01.csv
-rw-rw---- 1 superuser everyone     988 2023-12-06 09:19 LPAR_data.2023-12-01.on.csv
-rw-rw---- 1 superuser everyone   31247 2024-01-15 19:29 LPAR_resources.2024.ipynb
-rw-r----- 1 superuser everyone  379334 2023-11-12 18:50 Métodos Numéricos - Trabajo Colaborativo.ipynb
-rw-r----- 1 superuser everyone  381122 2023-11-12 18:50 Métodos Numéricos - Trabajo Colaborativo.ipynb
drwxr-x--- 3 superuser everyone    4096 2023-11-12 18:50 mnist_nn/
-rw-rw---- 1 superuser everyone     825 2024-01-14 17:37 model.pkl
-rw-r----- 1 superuser everyone   37754 2023-11-12 18:50 module_5_auto.csv
-rw-rw---- 1 superuser everyone     276 2023-12-14 14:53 movies_2.csv
-rw-rw---- 1 superuser everyone     280 2023-12-14 14:52 movies.csv
-rw-rw---- 1 superuser everyone 1662747 2024-01-16 17:52 PCA1.ipynb
drwxr-x--- 4 superuser everyone    4096 2023-11-20 20:04 PER8632/
-rw-r----- 1 superuser everyone     137 2023-11-12 18:50 preseveranceii_datos_vuelo_1.csv
-rw-rw---- 1 superuser everyone  850976 2024-01-11 20:57 PY0101EN-5.1_Intro_API.ipynb
drwxrwx--- 2 superuser everyone    4096 2023-12-16 17:14 __pycache__/
-rw-rw---- 1 superuser everyone   98604 2023-12-26 10:54 PySpark Dataframes.ipynb
-rw-rw---- 1 superuser everyone   56317 2023-11-20 22:27 PySpark_Machine_Learning.ipynb
drwxrwx--- 2 superuser everyone    4096 2023-12-30 18:12 Ramon_Barrios_Lascar/
-rw-r----- 1 superuser everyone    2810 2023-11-12 18:50 README
-rw-r----- 1 superuser everyone 4376218 2023-11-12 18:50 Resolución Métodos Numéricos.ipynb
-rw-r----- 1 superuser everyone 3605874 2023-11-12 18:50 Resolución Métodos Numéricos.ipynb
-rw-r----- 1 superuser everyone    8971 2023-11-12 18:50 simpleprime.ipynb
-rw-rw---- 1 superuser everyone  294367 2024-01-10 13:08 states_all.csv
-rw-rw---- 1 superuser everyone 3552196 2023-12-05 18:58 Tema 4 - Agregacion - MongoDB PyMongo - Taller.ipynb
-rw-r----- 1 superuser everyone    8633 2023-11-12 18:50 Termodinámica.ipynb
-rw-r----- 1 superuser everyone     534 2023-11-12 18:50 texput.log
drwxrwx--- 3 superuser everyone    4096 2024-01-07 16:32 TIA/
-rw-rw---- 1 superuser everyone   61194 2023-12-16 16:01 titanic.csv
-rw-rw---- 1 superuser everyone      72 2024-01-17 20:43 Untitled.ipynb
drwxrwx--- 4 superuser everyone    4096 2024-01-07 16:32 VIA/
-rw-r----- 1 superuser everyone  124103 2023-11-12 18:50 WDBC.dat
-rw-r----- 1 superuser everyone    4708 2023-11-12 18:50 WDBC.doc
drwxrwx--- 2 superuser everyone    4096 2023-11-12 18:50 work/
-rw-r----- 1 superuser everyone   44234 2023-11-12 18:50 WPBC.dat
-rw-r----- 1 superuser everyone    5671 2023-11-12 18:50 WPBC.doc
[superuser@Hermes ~]$

Para crear el contenedor para JupyterLab de Julia puedes usar:

docker run -hhostname--name=container-name-pexternal-port:internal-port-e JUPYTER_PORT=internal-port--cpus=Number --memory-reservation=MinimumMemory` --memory=*MaximumMemory* -d jupyter/jula-notebook`

[superuser@Hermes ~]$ docker run -h hermes --name julia -p 8887:8887 -v /share/Container/Jupyter:/home/jovyan -e JUPYTER_PORT=8887 --cpus=4 --memory-reservation=1024m --memory=16384m -d jupyter/julia-notebook 
Unable to find image 'jupyter/julia-notebook:latest' locally
latest: Pulling from jupyter/julia-notebook
aece8493d397: Already exists 
fd92c719666c: Already exists 
088f11eb1e74: Already exists 
4f4fb700ef54: Already exists 
ef8373d600b0: Already exists 
77e45ee945dc: Already exists 
a30f89a0af6c: Already exists 
dc42adc7eb73: Already exists 
abaa8376a650: Already exists 
aa099bb9e49a: Already exists 
822c4cbcf6a6: Already exists 
d25166dcdc7b: Already exists 
964fc3e4ff9f: Already exists 
2c4c69587ee4: Already exists 
de2cdd875fa8: Already exists 
75d33599f5f2: Already exists 
31973ea82470: Already exists 
96ee7e4439c7: Already exists 
1f9ad23c07ac: Already exists 
d19266e0cb17: Already exists 
c6c9cd74a7ad: Downloading [=======>                                           ]  23.18MB/148.6MB
8d67efbad291: Downloading [===========>                                       ]   21.5MB/96.06MB
... recortado ...

En el listado de arriba podemos ver el uso de algunas de las opciones:

  • Damos el nombre hostname interno de hermes porque el nombre del servidor linux donde se corren los contenedores se llama así. Esto no es obligatorio, pero es muy útil. Más adelante mostraré porqué es útil.
  • Damos el nombre julia al contenedor, así es más fácil buscarlo, iniciarlo, detenerlo, reconfigurarlo, ya que el nombre es obvio.
  • Especificamos dirigir el puerto TCP 8887 del servidor de contenedores al TCP 8887 del contenedor. Más adelante mostraré porqué es útil esta sintaxis.
  • Atamos un directorio externo en el servidor Linux a un directorio interno del contenedor. Así, si ponemos archivos en el directorio /share/Container/Jupyter del servidor el servidor verá esos archivos de manera predeterminada, sin copiar al espacio interno del contenedor. Ajusta eso para tu máquina o remueve la opción -v completa y sus argumentos, pero entonces necesitará del uso de la interfaz web de JupyterLab para dar los archivos de trabajo del contenedor, y usarán espacio interno del contenedor (en esto me refiero a la parte -v /share/Container/Jupyter:/home/jovyan en el ejemplo y no en la sintaxis mostrada arriba).
  • Asignamos a la variable JUPYTER_PORT el valor 8887 para dentro del contenedor. Usando esto especificamos que el servidor web de Julia estará disponible en el puerto TCP 8887. Este debe ser el mismo valor del segundo argumento de la opción -p.
  • (opcional) Especificamos un máximo de 4 cores de CPU para el contenedor. Ajusta para tu máquina o remueve completamente la opción --cpus.
  • (opcional) Especificamos un mínimo reservado de 1 GB de RAM para el contenedor. Ajusta para tu máquina o remueve completamente la opción--memory-reservation.
  • (opcional) Especificamos un máximo de 16 GB de RAM para el contenedor. Ajusta para tu máquina o remueve completamente la opción --memory.
  • -d indicará que el contenedor tendrá una consola desconectada, así que no verá los mensajes de inicio y ejecución pero podrá cerrar la ventana de los comandos en esta sesión y el notebook continuará su ejecución.
  • Le damos la imagen fuente para el contenedor con jupyter/julia-notebook.

En mi máquina de labortatorio tengo esto entonces:

[superuser@Hermes ~]$ docker run -h hermes --name julia -p 8887:8887 -v /share/Container/Jupyter:/home/jovyan -e JUPYTER_PORT=8887 --cpus=4 --memory-reservation=1024m --memory=16384m -d jupyter/julia-notebook 
Unable to find image 'jupyter/julia-notebook:latest' locally
latest: Pulling from jupyter/julia-notebook
aece8493d397: Already exists 
fd92c719666c: Already exists 
088f11eb1e74: Already exists 
4f4fb700ef54: Already exists 
ef8373d600b0: Already exists 
77e45ee945dc: Already exists 
a30f89a0af6c: Already exists 
dc42adc7eb73: Already exists 
abaa8376a650: Already exists 
aa099bb9e49a: Already exists 
822c4cbcf6a6: Already exists 
d25166dcdc7b: Already exists 
964fc3e4ff9f: Already exists 
2c4c69587ee4: Already exists 
de2cdd875fa8: Already exists 
75d33599f5f2: Already exists 
31973ea82470: Already exists 
96ee7e4439c7: Already exists 
1f9ad23c07ac: Already exists 
d19266e0cb17: Already exists 
c6c9cd74a7ad: Pull complete 
8d67efbad291: Pull complete 
Digest: sha256:809d1b3bbe2ef17f1725f3b0d5cca5c3d7ad9dd3de84655c7df0305b4cd7665d
Status: Downloaded newer image for jupyter/julia-notebook:latest
73d6e2dd5afed7311deacda777362b1299b32c253b18e24e19e6603c5d0ac4dc
[superuser@Hermes ~]$ 

y esto …

[superuser@Hermes ~]$ docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED          STATUS                    PORTS                                                                                                                                                                                                   NAMES
73d6e2dd5afe   jupyter/julia-notebook      "tini -g -- start-no…"   10 minutes ago   Up 10 minutes (healthy)   0.0.0.0:8887->8887/tcp, 8888/tcp                                                                                                                                                                        julia
0561512268d9   rbl/jupyter-spark-py-jl-r   "tini -g -- start-no…"   3 weeks ago      Up 9 days (unhealthy)     0.0.0.0:4040->4040/tcp, 0.0.0.0:8888->8888/tcp                                                                                                                                                          jupyter
a22ff2761a42   cassandra                   "docker-entrypoint.s…"   6 weeks ago      Up 9 days                 0.0.0.0:32772->7000/tcp, 0.0.0.0:32771->7001/tcp, 0.0.0.0:32770->7199/tcp, 0.0.0.0:32769->9042/tcp, 0.0.0.0:32768->9160/tcp                                                                             cassandra
8f0446c3557e   linuxserver/plex:latest     "/init"                  7 months ago     Up 3 weeks                0.0.0.0:3005->3005/tcp, 0.0.0.0:8324->8324/tcp, 0.0.0.0:32410->32410/udp, 0.0.0.0:32400->32400/tcp, 1900/udp, 0.0.0.0:32469->32469/tcp, 0.0.0.0:32412-32414->32412-32414/udp, 0.0.0.0:15353->5353/udp   plex
fa74a19d31ee   andyshinn/dnsmasq:latest    "dnsmasq -k"             2 years ago      Up 8 days                                                                                                                                                                                                                         dnsmasq
[superuser@Hermes ~]$ 

Nótese que hay un contenedor jupyter/julia-notebook corriendo con nombre julia.

Para acceder a los servicios del notebook va a necesitar la URL de Jupyter:

[superuser@Hermes ~]$ docker logs julia 2>&1 | grep http:.*?token | tail -n 2 | head -n1
        http://hermes:8887/lab?token=fb59c49810adde8957389fa1d4a03d18c949abe198015637
[superuser@Hermes ~]$

Nótese cómo ese comando de linux nos da el nombre hostname del servidor Linux (porque usamos -h hermes) y con el puerto TCP que especificamos. Abrimos el URL y obtenemos algo como la imagen a continuación:

Figure 3.. Initial JupyterLab interface.

Figure 3.. Initial JupyterLab interface.

Debido a que usamos la URL completa con el token, no se nos pedirá una contraseña o un token de acceso. Ya podemos usar notebooks con el kernel de Julia.

Notebooks en lenguaje Julia

Usando el botón familiar (arriba) en la interfaz de JupyterLab creamos un nuevo notebook de Julia y encontraremos la interfaz ya familiar:

Figure 4 New notebook showing the kernel language indicator/selector.

Figure 4 New notebook showing the kernel language indicator/selector.

Ahora, ya teniendo un notebook nuevo, simplemente podemos usar código Julia, pero creo que llegamos aquí para usar PDDL. Comencemos.

  • Necesitaremos agregar algunos paquetes no predeterminados a la instalación base de Julia.
using Pkg;
  • Necesitaremos cargar unos paquetes, pero debido a que no están en la base, necesitaremos instalarlos primero.
FLAG = true;
if ! @isdefined(PDDL) && FLAG
    Pkg.add("PDDL")
    Pkg.add("PDDLViz")
    Pkg.add("GLMakie")
    Pkg.add("PlanningDomains")
    Pkg.add(url="https://github.com/JuliaPlanners/SymbolicPlanners.jl")
else
    println("Skipped reinstallation")
end;

Dependiendo de la velocidad del sistema donde está corriendo el contenedor este paso puede fácilmente tardar 25 o 30 minutos, Julia prefiere usar paquetes precompilados, y si solo fuentes están disponibles, descargará, compilará e instalará los paquetes que estamos pidiendo. Por favor sé paciente (descargar y recompilar sólo se necesita una vez por contenedor).

Figure 5. Notebook while adding (and compiling) the needed additional packages.

Figure 5. Notebook while adding (and compiling) the needed additional packages.

  • Después de la instalación solo se necesita indicar que se usen los paquetes adicionales.
using PDDL, SymbolicPlanners;
  • Ahora ya está listo para definir sus dominios y sus problemas PDDL:
a_domain = parse_domain(" un string definiendo su dominio ")

Nota que si no terminas una celda con un punto y coma (;) tendrás un resultado, y Jupyter intenta mostrar esos resultados. Si no quieres ver el resultado, termina tu celda —técnicamente, la última expresión de la celda del notebook— con un punto y coma (;).

a_problem = parse_problem(" un string definiendo el problema ")

Figure 6 Julia Notebook after loading a domain from a string.

Figure 6 Julia Notebook after loading a domain from a string.

«Ramón, yo tengo algunos archivos de dominio y problemas .pddl y no quiero estar copiando y pegando el contenido de esos archivos en la celda del notebook». ¡No hay problema! Se pueden usar unas funciones de Julia para cargar esos archivos:

f_domain = load_domain( "filename of domain.pddl" )
f_problem = load_problem( "filename of problem.pddl" )

De cualquiera de las dos maneras terminará con objetos dominio PDDL y problema PDDL.

  • Ahora necesita inicializar el ambiente con initstate (que devuelve un objeto que se necesitará más adelante, no lo pierdas).
initial = initstate( a_domain, a_problem )
  • Ahora se necesita de una manera de definir las métricas para la meta (costo mínimo, cantidad mínima de pasos y así):
the_goals = MinStepsGoal( a_problem )
  • Necesitaremos un planner, que es el objeto que definirá el algoritmo que busca los planes. Algunos de estos necesitan que se les de una función heurística, otros no:
a_planner = BreadthFirstPlanner()
  • Y finalmente necesitará un solver que combinará todo y le dará acceso a las métricas y especialmente a los pasos solución del plan:
solved = a_planner( a_domain, initial, the_goals);

La documentación de los módulos PDDL y SymbolicPlanner le darán más información acerca de los procedimientos. Recuerde, si finaliza una celda de código con un punto y coma Jupyter recibirá un valor de retorno y no le mostrará el mismo, entonces necesitará de una manera de ver el plan. Aquí hay una manera simple:

println("Whole plan: (", length(solved), ") steps")
for s in collect(solved)
 println("    ", s)
end;

Figure 7 Julia Notebook after loading a problem, creating a goal, a measuring object, a solver and solving the problem.

Figure 7 Julia Notebook after loading a problem, creating a goal, a measuring object, a solver and solving the problem.

Nótese cómo en nuestro ejemplo el método BreadFirstPlanner() resolvió el problema diciendo "el plan simple de un solo elemento es ir con el carro rojo del pueblo town1 al town2".

Hay mucho más en el uso de Julia para resolver sus problemas de planificación clásica en formato PDDL, pero ese es contenido para otro día.

References:


메타데이터
post_id
cd4e6a6d4026
slug
cómo-correr-fácilmente-solvers-pddl-en-tu-propia-infraestructura-cd4e6a6d4026
url
https://medium.com/@ramonbarrioslascar/c%C3%B3mo-correr-f%C3%A1cilmente-solvers-pddl-en-tu-propia-infraestructura-cd4e6a6d4026
canonical_url
https://medium.com/@ramonbarrioslascar/c%C3%B3mo-correr-f%C3%A1cilmente-solvers-pddl-en-tu-propia-infraestructura-cd4e6a6d4026
author_url
https://medium.com/@ramonbarrioslascar
status
ok
fetched_at
2026-06-28 10:39:35