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…
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 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
8887del servidor de contenedores al TCP8887del 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/Jupyterdel 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-vcompleta 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/jovyanen el ejemplo y no en la sintaxis mostrada arriba). - Asignamos a la variable JUPYTER_PORT el valor
8887para dentro del contenedor. Usando esto especificamos que el servidor web de Julia estará disponible en el puerto TCP8887. 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. -dindicará 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.
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.
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.
- 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.
«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.
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:
- SymbolicPlanners.jl: Symbolic planners for problems and domains specified in PDDL.
- PDDL.jl: PDDL.jl
메타데이터
- 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