11. Plot library
This python notebook shows how to use the plot package of the Unified Planning.
11.1. Setup
We start by downloading (from github) the unified planning library.
[1]:
!apt install graphviz graphviz-dev
!pip install --pre unified-planning[plot]
E: Could not open lock file /var/lib/dpkg/lock-frontend - open (13: Permission denied)
E: Unable to acquire the dpkg frontend lock (/var/lib/dpkg/lock-frontend), are you root?
Collecting unified-planning[plot]
Downloading unified_planning-0.6.0.273.dev1-py3-none-any.whl (635 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 635.7/635.7 kB 13.4 MB/s eta 0:00:00
Requirement already satisfied: pyparsing in /home/docs/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages (from unified-planning[plot]) (3.1.0)
Requirement already satisfied: networkx in /home/docs/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages (from unified-planning[plot]) (3.1)
Collecting plotly (from unified-planning[plot])
Downloading plotly-5.15.0-py2.py3-none-any.whl (15.5 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 15.5/15.5 MB 72.3 MB/s eta 0:00:00
Collecting matplotlib (from unified-planning[plot])
Downloading matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.6 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.6/11.6 MB 74.6 MB/s eta 0:00:00
Collecting kaleido (from unified-planning[plot])
Downloading kaleido-0.2.1-py2.py3-none-manylinux1_x86_64.whl (79.9 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 79.9/79.9 MB 23.2 MB/s eta 0:00:00
Collecting pygraphviz (from unified-planning[plot])
Downloading pygraphviz-1.11.zip (120 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 120.8/120.8 kB 25.1 MB/s eta 0:00:00
Preparing metadata (setup.py) ... - done
Collecting pandas (from unified-planning[plot])
Downloading pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (12.3 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 12.3/12.3 MB 73.6 MB/s eta 0:00:00
Collecting contourpy>=1.0.1 (from matplotlib->unified-planning[plot])
Downloading contourpy-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (300 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 300.7/300.7 kB 43.4 MB/s eta 0:00:00
Collecting cycler>=0.10 (from matplotlib->unified-planning[plot])
Downloading cycler-0.11.0-py3-none-any.whl (6.4 kB)
Collecting fonttools>=4.22.0 (from matplotlib->unified-planning[plot])
Downloading fonttools-4.40.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.2 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.2/4.2 MB 99.5 MB/s eta 0:00:00
Collecting kiwisolver>=1.0.1 (from matplotlib->unified-planning[plot])
Downloading kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.6 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.6/1.6 MB 90.0 MB/s eta 0:00:00
Collecting numpy>=1.20 (from matplotlib->unified-planning[plot])
Downloading numpy-1.25.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (17.6 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 17.6/17.6 MB 69.5 MB/s eta 0:00:00
Requirement already satisfied: packaging>=20.0 in /home/docs/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages (from matplotlib->unified-planning[plot]) (23.1)
Requirement already satisfied: pillow>=6.2.0 in /home/docs/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages (from matplotlib->unified-planning[plot]) (9.5.0)
Requirement already satisfied: python-dateutil>=2.7 in /home/docs/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages (from matplotlib->unified-planning[plot]) (2.8.2)
Collecting pytz>=2020.1 (from pandas->unified-planning[plot])
Downloading pytz-2023.3-py2.py3-none-any.whl (502 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 502.3/502.3 kB 59.5 MB/s eta 0:00:00
Collecting tzdata>=2022.1 (from pandas->unified-planning[plot])
Downloading tzdata-2023.3-py2.py3-none-any.whl (341 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 341.8/341.8 kB 44.2 MB/s eta 0:00:00
Collecting tenacity>=6.2.0 (from plotly->unified-planning[plot])
Downloading tenacity-8.2.2-py3-none-any.whl (24 kB)
Requirement already satisfied: six>=1.5 in /home/docs/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib->unified-planning[plot]) (1.16.0)
Building wheels for collected packages: pygraphviz
Building wheel for pygraphviz (setup.py) ... - \ | done
Created wheel for pygraphviz: filename=pygraphviz-1.11-cp310-cp310-linux_x86_64.whl size=220189 sha256=bef02c74284d3b867abf27e79a26efdab1d05866c7aa6b3a482855f567417f2c
Stored in directory: /home/docs/.cache/pip/wheels/5b/ee/36/f47a0d35664fbe1a2b5a433ae33c6ad636b00bb231f68a9aaa
Successfully built pygraphviz
Installing collected packages: pytz, kaleido, unified-planning, tzdata, tenacity, pygraphviz, numpy, kiwisolver, fonttools, cycler, plotly, pandas, contourpy, matplotlib
Successfully installed contourpy-1.1.0 cycler-0.11.0 fonttools-4.40.0 kaleido-0.2.1 kiwisolver-1.4.4 matplotlib-3.7.1 numpy-1.25.0 pandas-2.0.3 plotly-5.15.0 pygraphviz-1.11 pytz-2023.3 tenacity-8.2.2 tzdata-2023.3 unified-planning-0.6.0.273.dev1
We are now ready to plot some plans using the Unified-Planning library!
11.2. Imports
We need to import all the types of plan we want to plot, the plot functions and the actions.
[2]:
import unified_planning
from unified_planning.plans import (
SequentialPlan,
TimeTriggeredPlan,
PartialOrderPlan,
ContingentPlanNode,
ContingentPlan,
STNPlanNode,
STNPlan,
)
from unified_planning.plot import (
plot_plan, # plot_plan plots all the types of plans, but is not customizable, while specific methods
plot_sequential_plan,
plot_time_triggered_plan,
plot_partial_order_plan,
plot_contingent_plan,
plot_stn_plan,
plot_causal_graph,
)
from unified_planning.model import InstantaneousAction, DurativeAction, TimepointKind, Fluent, Problem, Object
from unified_planning.shortcuts import BoolType, UserType, IntType, RealType
Ignore the code below, it’s used to make this notebook also runnable in the Countinuous Intergation.
[3]:
# Redefine the plot package methods imported above to print the plot to a temp file
# if the exception "could not locate runnable browser" is raised. This usually happens
# in the Continuous Integration.
from inspect import getmembers, isfunction
from unified_planning import plot
from functools import partial
import os, uuid, tempfile as tf
# Define the function that will be executed instead
def _function(original_function, *args, **kwargs):
try:
original_function(*args, **kwargs)
except Exception as e:
if "could not locate runnable browser" in str(e):
original_function(*args, **kwargs,
filename=f"{os.path.join(tf.gettempdir(), str(uuid.uuid1()))}.png"
)
else:
raise e
# Iterate over all the functions of the plot package
for function_name, function in getmembers(plot, isfunction):
# Override the original function with the new one
globals()[function_name] = partial(_function, function)
11.3. Sequential Plan
A SequentialPlan is simply a sequence of actions.
[4]:
actions = list((InstantaneousAction(f"a{i}") for i in range(1, 5)))
sequential_plan = SequentialPlan([a() for a in actions])
plot_sequential_plan(sequential_plan, font_size=14)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1361, in AGraph._get_prog(self, prog)
1360 try: # user must pick one of the graphviz programs...
-> 1361 runprog = self._which(prog)
1362 except:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1816, in AGraph._which(self, name)
1815 return match[0]
-> 1816 raise ValueError(f"No prog {name} in path.")
ValueError: No prog dot in path.
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
Cell In[4], line 4
1 actions = list((InstantaneousAction(f"a{i}") for i in range(1, 5)))
2 sequential_plan = SequentialPlan([a() for a in actions])
----> 4 plot_sequential_plan(sequential_plan, font_size=14)
Cell In[3], line 20, in _function(original_function, *args, **kwargs)
16 original_function(*args, **kwargs,
17 filename=f"{os.path.join(tf.gettempdir(), str(uuid.uuid1()))}.png"
18 )
19 else:
---> 20 raise e
Cell In[3], line 13, in _function(original_function, *args, **kwargs)
11 def _function(original_function, *args, **kwargs):
12 try:
---> 13 original_function(*args, **kwargs)
14 except Exception as e:
15 if "could not locate runnable browser" in str(e):
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/plan_plot.py:179, in plot_sequential_plan(plan, problem, expression_or_expressions, metric_or_metrics, filename, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, draw_networkx_kwargs)
177 graph.add_edge(current_ai, next_ai)
178 current_ai = next_ai
--> 179 fig, _, _ = draw_base_graph(
180 graph,
181 figsize=figsize,
182 top_bottom=top_bottom,
183 generate_node_label=generate_node_label,
184 arrowsize=arrowsize,
185 node_size=node_size,
186 node_color=node_color,
187 edge_color=edge_color,
188 font_size=font_size,
189 font_color=font_color,
190 draw_networkx_kwargs=draw_networkx_kwargs,
191 )
192 if filename is None:
193 plt.show()
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:95, in draw_base_graph(graph, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, draw_networkx_kwargs)
92 fig = plt.figure(figsize=figsize)
93 ax = fig.add_subplot()
---> 95 pos = _generate_positions(graph, prog="dot", top_bottom=top_bottom)
97 nx.draw_networkx(
98 graph,
99 pos,
(...)
110 **draw_networkx_kwargs,
111 )
112 return fig, ax, pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:139, in _generate_positions(graph, prog, top_bottom)
136 else:
137 new_graph.graph.setdefault("graph", {})["rankdir"] = "LR"
--> 139 new_pos = nx.nx_agraph.graphviz_layout(new_graph, prog=prog)
141 pos = {id_to_node[i]: value for i, value in new_pos.items()}
143 return pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:251, in graphviz_layout(G, prog, root, args)
220 def graphviz_layout(G, prog="neato", root=None, args=""):
221 """Create node positions for G using Graphviz.
222
223 Parameters
(...)
249 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
250 """
--> 251 return pygraphviz_layout(G, prog=prog, root=root, args=args)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:303, in pygraphviz_layout(G, prog, root, args)
301 args += f"-Groot={root}"
302 A = to_agraph(G)
--> 303 A.layout(prog=prog, args=args)
304 node_pos = {}
305 for n in G:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1483, in AGraph.layout(self, prog, args)
1466 """Assign positions to nodes in graph.
1467
1468 Optional prog=['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
(...)
1480
1481 """
1482 output_fmt = "dot"
-> 1483 data = self._run_prog(prog, " ".join([args, "-T", output_fmt]))
1484 self.from_string(data)
1485 self.has_layout = True
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1377, in AGraph._run_prog(self, prog, args)
1367 def _run_prog(self, prog="nop", args=""):
1368 """Apply graphviz program to graph and return the result as a string.
1369
1370 >>> import pygraphviz as pgv
(...)
1375 Use keyword args to add additional arguments to graphviz programs.
1376 """
-> 1377 runprog = r'"%s"' % self._get_prog(prog)
1378 cmd = " ".join([runprog, args])
1379 dotargs = shlex.split(cmd)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1363, in AGraph._get_prog(self, prog)
1361 runprog = self._which(prog)
1362 except:
-> 1363 raise ValueError(f"Program {prog} not found in path.")
1365 return runprog
ValueError: Program dot not found in path.
11.3.1. Plotting fluents in SequentialPlan
On a SequentialPlan, if the UPSequentialSimulator (in the up.engines.sequential_simulator.py) supports the Problem, the value of some arbitrary expressions or the value of some quality metrics can be plotted. This shows how the value changes during the plan.
Only numeric or boolean expressions can be plotted.
11.3.1.1. Define the problem
The problem defined is a robot that moves from l1 to l4, passing through l2 and l3.
While moving, the battery decreases based on the distance from the locations and the total distance accumulates.
Note: the problem has specified only the parts interesting for this simulation. It’s not a complete problem to give to a planner.
[5]:
# Define the UserType
Location = UserType("Location")
# Define fluents
robot_at = Fluent("robot_at", BoolType(), position=Location)
battery_charge = Fluent("battery_charge", RealType(0, 100))
distance = Fluent("distance", IntType(), l_from=Location, l_to=Location)
total_distance = Fluent("total_distance", IntType())
# Define the move action
move = InstantaneousAction("move", l_from=Location, l_to=Location)
l_from = move.parameter("l_from")
l_to = move.parameter("l_to")
move.add_precondition(robot_at(l_from))
move.add_effect(robot_at(l_from), False)
move.add_effect(robot_at(l_to), True)
move.add_decrease_effect(battery_charge, distance(l_from, l_to)/2 + 5)
move.add_increase_effect(total_distance, distance(l_from, l_to))
# Define the Location Objects
objects = list((Object(f"l{i}", Location) for i in range(1, 5)))
l1, l2, l3, l4 = objects
# Create the problem, add fluents, the move action and the objects
problem = Problem("moving robot")
problem.add_fluent(robot_at, default_initial_value=False)
problem.add_fluent(battery_charge, default_initial_value=100)
problem.add_fluent(distance, default_initial_value=0)
problem.add_fluent(total_distance, default_initial_value=0)
problem.add_action(move)
problem.add_objects(objects)
# Set the initial values different from the defaults
problem.set_initial_value(robot_at(l1), True)
problem.set_initial_value(distance(l1, l2), 26)
problem.set_initial_value(distance(l2, l3), 50)
problem.set_initial_value(distance(l3, l4), 10)
# Create the plan to simulate
plan = SequentialPlan([
move(l1, l2),
move(l2, l3),
move(l3, l4),
])
[6]:
plot_sequential_plan(plan, problem, [battery_charge, total_distance])
11.4. TimeTriggeredPlan
A TimeTriggeredPlan is a sequence of 3 items. Every items is composed by an action, the time in which the action starts, and, if the action is a DurativeAction, the action duration.
[7]:
a1, a2, a3, a4 = (DurativeAction(f"a{i}") for i in range(1, 5))
time_triggered_plan = TimeTriggeredPlan([
(0, a1(), 3),
(1, a2(), 3),
(2, a3(), 2),
(2, a4(), 1),
])
plot_plan(time_triggered_plan)
11.5. PartialOrderPlan
A PartialOrderPlan is a directed graph where the nodes are ActionInstances and the edges create an ordering between 2 actions.
In the unified_planning, a ParitalOrderPlan is created with the adjacency list.
Generally speaking, a SequentialPlan is a specific case of a PartialOrderPlan where every action has only one possible action that must be done before and one action that must be done later.
[8]:
ai1, ai2, ai3, ai4, ai5, ai6, ai7, ai8 = (InstantaneousAction(f"a{i}")() for i in range(1, 9))
partial_order_plan = PartialOrderPlan({
ai1: [ai2],
ai2: [ai4],
ai3: [ai4],
ai4: [ai5, ai6],
ai5: [ai7],
ai6: [ai8],
ai7: [ai8],
})
plot_partial_order_plan(partial_order_plan, font_size=16)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1361, in AGraph._get_prog(self, prog)
1360 try: # user must pick one of the graphviz programs...
-> 1361 runprog = self._which(prog)
1362 except:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1816, in AGraph._which(self, name)
1815 return match[0]
-> 1816 raise ValueError(f"No prog {name} in path.")
ValueError: No prog dot in path.
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
Cell In[8], line 12
1 ai1, ai2, ai3, ai4, ai5, ai6, ai7, ai8 = (InstantaneousAction(f"a{i}")() for i in range(1, 9))
2 partial_order_plan = PartialOrderPlan({
3 ai1: [ai2],
4 ai2: [ai4],
(...)
9 ai7: [ai8],
10 })
---> 12 plot_partial_order_plan(partial_order_plan, font_size=16)
Cell In[3], line 20, in _function(original_function, *args, **kwargs)
16 original_function(*args, **kwargs,
17 filename=f"{os.path.join(tf.gettempdir(), str(uuid.uuid1()))}.png"
18 )
19 else:
---> 20 raise e
Cell In[3], line 13, in _function(original_function, *args, **kwargs)
11 def _function(original_function, *args, **kwargs):
12 try:
---> 13 original_function(*args, **kwargs)
14 except Exception as e:
15 if "could not locate runnable browser" in str(e):
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/plan_plot.py:587, in plot_partial_order_plan(plan, filename, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, draw_networkx_kwargs)
547 """
548 This method plots the Contingent plan as a directed graph where the edges are
549 labeled with the temporal constraints contained in the given ``plan``.
(...)
583 maintained in any way and it might be removed or modified at any moment.
584 """
585 import matplotlib.pyplot as plt # type: ignore[import]
--> 587 fig, _, _ = draw_base_graph(
588 plan._graph,
589 figsize=figsize,
590 top_bottom=top_bottom,
591 generate_node_label=generate_node_label,
592 arrowsize=arrowsize,
593 node_size=node_size,
594 node_color=node_color,
595 edge_color=edge_color,
596 font_size=font_size,
597 font_color=font_color,
598 draw_networkx_kwargs=draw_networkx_kwargs,
599 )
600 if filename is None:
601 plt.show()
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:95, in draw_base_graph(graph, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, draw_networkx_kwargs)
92 fig = plt.figure(figsize=figsize)
93 ax = fig.add_subplot()
---> 95 pos = _generate_positions(graph, prog="dot", top_bottom=top_bottom)
97 nx.draw_networkx(
98 graph,
99 pos,
(...)
110 **draw_networkx_kwargs,
111 )
112 return fig, ax, pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:139, in _generate_positions(graph, prog, top_bottom)
136 else:
137 new_graph.graph.setdefault("graph", {})["rankdir"] = "LR"
--> 139 new_pos = nx.nx_agraph.graphviz_layout(new_graph, prog=prog)
141 pos = {id_to_node[i]: value for i, value in new_pos.items()}
143 return pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:251, in graphviz_layout(G, prog, root, args)
220 def graphviz_layout(G, prog="neato", root=None, args=""):
221 """Create node positions for G using Graphviz.
222
223 Parameters
(...)
249 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
250 """
--> 251 return pygraphviz_layout(G, prog=prog, root=root, args=args)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:303, in pygraphviz_layout(G, prog, root, args)
301 args += f"-Groot={root}"
302 A = to_agraph(G)
--> 303 A.layout(prog=prog, args=args)
304 node_pos = {}
305 for n in G:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1483, in AGraph.layout(self, prog, args)
1466 """Assign positions to nodes in graph.
1467
1468 Optional prog=['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
(...)
1480
1481 """
1482 output_fmt = "dot"
-> 1483 data = self._run_prog(prog, " ".join([args, "-T", output_fmt]))
1484 self.from_string(data)
1485 self.has_layout = True
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1377, in AGraph._run_prog(self, prog, args)
1367 def _run_prog(self, prog="nop", args=""):
1368 """Apply graphviz program to graph and return the result as a string.
1369
1370 >>> import pygraphviz as pgv
(...)
1375 Use keyword args to add additional arguments to graphviz programs.
1376 """
-> 1377 runprog = r'"%s"' % self._get_prog(prog)
1378 cmd = " ".join([runprog, args])
1379 dotargs = shlex.split(cmd)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1363, in AGraph._get_prog(self, prog)
1361 runprog = self._which(prog)
1362 except:
-> 1363 raise ValueError(f"Program {prog} not found in path.")
1365 return runprog
ValueError: Program dot not found in path.
11.6. STNPlan
An STNPlan represents the temporal constraints from the events that must be performed during the plan.
An event is either the start of the plan, the end of the plan, the start of an action or the end of an action.
It is represented as a directed graph, where the nodes are the events.
There is an edge from the node N to the node M if N and M have a relative temporal constraint. The edge is labeled with 2 numbers, the lower bound and the upper bound to the time that can elapse from N to M.
For example, N –[0, 7]–> M means that M must happen after -or at the same time, since 0 is included- N, but no more than 7 time units later.
[9]:
ai1, ai2, ai3, ai4 = (InstantaneousAction(f"a{i}")() for i in range(1, 5))
start_a1 = STNPlanNode(TimepointKind.START, ai1)
end_a1 = STNPlanNode(TimepointKind.END, ai1)
start_a2 = STNPlanNode(TimepointKind.START, ai2)
end_a2 = STNPlanNode(TimepointKind.END, ai2)
start_a3 = STNPlanNode(TimepointKind.START, ai3)
end_a3 = STNPlanNode(TimepointKind.END, ai3)
start_a4 = STNPlanNode(TimepointKind.START, ai4)
end_a4 = STNPlanNode(TimepointKind.END, ai4)
stn_plan = STNPlan([
(start_a1, 1, 1, end_a1), # Link start to end actions
(start_a2, 0, 3, end_a2),
(start_a3, 0, 3, end_a3),
(start_a4, 0, 3, end_a4),
(end_a1, 0, None, start_a2), # Action 1 must finish before action 2 start (or in the same moment)
(end_a3, None, 1, end_a4), # Action 3 can finish AT most 1 after the end of Action4
])
plot_stn_plan(stn_plan)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1361, in AGraph._get_prog(self, prog)
1360 try: # user must pick one of the graphviz programs...
-> 1361 runprog = self._which(prog)
1362 except:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1816, in AGraph._which(self, name)
1815 return match[0]
-> 1816 raise ValueError(f"No prog {name} in path.")
ValueError: No prog dot in path.
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
Cell In[9], line 19
9 end_a4 = STNPlanNode(TimepointKind.END, ai4)
10 stn_plan = STNPlan([
11 (start_a1, 1, 1, end_a1), # Link start to end actions
12 (start_a2, 0, 3, end_a2),
(...)
16 (end_a3, None, 1, end_a4), # Action 3 can finish AT most 1 after the end of Action4
17 ])
---> 19 plot_stn_plan(stn_plan)
Cell In[3], line 20, in _function(original_function, *args, **kwargs)
16 original_function(*args, **kwargs,
17 filename=f"{os.path.join(tf.gettempdir(), str(uuid.uuid1()))}.png"
18 )
19 else:
---> 20 raise e
Cell In[3], line 13, in _function(original_function, *args, **kwargs)
11 def _function(original_function, *args, **kwargs):
12 try:
---> 13 original_function(*args, **kwargs)
14 except Exception as e:
15 if "could not locate runnable browser" in str(e):
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/plan_plot.py:398, in plot_stn_plan(plan, filename, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, generate_edge_label, edge_font_size, edge_font_color, draw_networkx_kwargs, draw_networkx_edge_labels_kwargs)
393 graph.add_edge(left_node, right_node)
394 edge_labels[(left_node, right_node)] = edge_label_function(
395 lower_bound, upper_bound
396 )
--> 398 fig, ax, pos = draw_base_graph(
399 graph,
400 figsize=figsize,
401 top_bottom=top_bottom,
402 generate_node_label=generate_node_label,
403 arrowsize=arrowsize,
404 node_size=node_size,
405 node_color=node_color,
406 edge_color=edge_color,
407 font_size=font_size,
408 font_color=font_color,
409 draw_networkx_kwargs=draw_networkx_kwargs,
410 )
411 nx.draw_networkx_edge_labels(
412 graph,
413 pos,
(...)
418 **draw_networkx_edge_labels_kwargs,
419 )
420 if filename is None:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:95, in draw_base_graph(graph, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, draw_networkx_kwargs)
92 fig = plt.figure(figsize=figsize)
93 ax = fig.add_subplot()
---> 95 pos = _generate_positions(graph, prog="dot", top_bottom=top_bottom)
97 nx.draw_networkx(
98 graph,
99 pos,
(...)
110 **draw_networkx_kwargs,
111 )
112 return fig, ax, pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:139, in _generate_positions(graph, prog, top_bottom)
136 else:
137 new_graph.graph.setdefault("graph", {})["rankdir"] = "LR"
--> 139 new_pos = nx.nx_agraph.graphviz_layout(new_graph, prog=prog)
141 pos = {id_to_node[i]: value for i, value in new_pos.items()}
143 return pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:251, in graphviz_layout(G, prog, root, args)
220 def graphviz_layout(G, prog="neato", root=None, args=""):
221 """Create node positions for G using Graphviz.
222
223 Parameters
(...)
249 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
250 """
--> 251 return pygraphviz_layout(G, prog=prog, root=root, args=args)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:303, in pygraphviz_layout(G, prog, root, args)
301 args += f"-Groot={root}"
302 A = to_agraph(G)
--> 303 A.layout(prog=prog, args=args)
304 node_pos = {}
305 for n in G:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1483, in AGraph.layout(self, prog, args)
1466 """Assign positions to nodes in graph.
1467
1468 Optional prog=['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
(...)
1480
1481 """
1482 output_fmt = "dot"
-> 1483 data = self._run_prog(prog, " ".join([args, "-T", output_fmt]))
1484 self.from_string(data)
1485 self.has_layout = True
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1377, in AGraph._run_prog(self, prog, args)
1367 def _run_prog(self, prog="nop", args=""):
1368 """Apply graphviz program to graph and return the result as a string.
1369
1370 >>> import pygraphviz as pgv
(...)
1375 Use keyword args to add additional arguments to graphviz programs.
1376 """
-> 1377 runprog = r'"%s"' % self._get_prog(prog)
1378 cmd = " ".join([runprog, args])
1379 dotargs = shlex.split(cmd)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1363, in AGraph._get_prog(self, prog)
1361 runprog = self._which(prog)
1362 except:
-> 1363 raise ValueError(f"Program {prog} not found in path.")
1365 return runprog
ValueError: Program dot not found in path.
11.7. ContingentPlan
A ContingentPlan is represented as a decision tree. Some actions can sense the initially unknown value of one or more fluents and decide which action to perform next based on the sensed values.
It is represented as a directed graph, with the nodes containing the action to perform and the edges labeled as the expression that must be True in order to take that branch.
[10]:
from unified_planning.shortcuts import *
ai1, ai2, ai3, ai4, ai5 = (InstantaneousAction(f"a{i}")() for i in range(1, 6))
node_1 = ContingentPlanNode(ai1)
node_2 = ContingentPlanNode(ai2)
node_3 = ContingentPlanNode(ai3)
node_4 = ContingentPlanNode(ai4)
node_5 = ContingentPlanNode(ai5)
x, y = Fluent("x"), Fluent("y")
node_1.add_child({x(): TRUE()}, node_2)
node_1.add_child({x(): FALSE()}, node_3)
node_3.add_child({y(): TRUE()}, node_4)
node_3.add_child({y(): FALSE()}, node_5)
contingent_plan = ContingentPlan(node_1)
plot_contingent_plan(contingent_plan, font_size = 14, edge_font_size = 12)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1361, in AGraph._get_prog(self, prog)
1360 try: # user must pick one of the graphviz programs...
-> 1361 runprog = self._which(prog)
1362 except:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1816, in AGraph._which(self, name)
1815 return match[0]
-> 1816 raise ValueError(f"No prog {name} in path.")
ValueError: No prog dot in path.
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
Cell In[10], line 15
12 node_3.add_child({y(): FALSE()}, node_5)
13 contingent_plan = ContingentPlan(node_1)
---> 15 plot_contingent_plan(contingent_plan, font_size = 14, edge_font_size = 12)
Cell In[3], line 20, in _function(original_function, *args, **kwargs)
16 original_function(*args, **kwargs,
17 filename=f"{os.path.join(tf.gettempdir(), str(uuid.uuid1()))}.png"
18 )
19 else:
---> 20 raise e
Cell In[3], line 13, in _function(original_function, *args, **kwargs)
11 def _function(original_function, *args, **kwargs):
12 try:
---> 13 original_function(*args, **kwargs)
14 except Exception as e:
15 if "could not locate runnable browser" in str(e):
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/plan_plot.py:504, in plot_contingent_plan(plan, filename, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, generate_edge_label, edge_font_size, edge_font_color, draw_networkx_kwargs, draw_networkx_edge_labels_kwargs)
501 graph.add_edge(node, child)
502 edge_labels[(node, child)] = edge_label_function(fluents)
--> 504 fig, ax, pos = draw_base_graph(
505 graph,
506 figsize=figsize,
507 top_bottom=top_bottom,
508 generate_node_label=generate_node_label,
509 arrowsize=arrowsize,
510 node_size=node_size,
511 node_color=node_color,
512 edge_color=edge_color,
513 font_size=font_size,
514 font_color=font_color,
515 draw_networkx_kwargs=draw_networkx_kwargs,
516 )
517 nx.draw_networkx_edge_labels(
518 graph,
519 pos,
(...)
524 **draw_networkx_edge_labels_kwargs,
525 )
526 if filename is None:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:95, in draw_base_graph(graph, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, draw_networkx_kwargs)
92 fig = plt.figure(figsize=figsize)
93 ax = fig.add_subplot()
---> 95 pos = _generate_positions(graph, prog="dot", top_bottom=top_bottom)
97 nx.draw_networkx(
98 graph,
99 pos,
(...)
110 **draw_networkx_kwargs,
111 )
112 return fig, ax, pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:139, in _generate_positions(graph, prog, top_bottom)
136 else:
137 new_graph.graph.setdefault("graph", {})["rankdir"] = "LR"
--> 139 new_pos = nx.nx_agraph.graphviz_layout(new_graph, prog=prog)
141 pos = {id_to_node[i]: value for i, value in new_pos.items()}
143 return pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:251, in graphviz_layout(G, prog, root, args)
220 def graphviz_layout(G, prog="neato", root=None, args=""):
221 """Create node positions for G using Graphviz.
222
223 Parameters
(...)
249 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
250 """
--> 251 return pygraphviz_layout(G, prog=prog, root=root, args=args)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:303, in pygraphviz_layout(G, prog, root, args)
301 args += f"-Groot={root}"
302 A = to_agraph(G)
--> 303 A.layout(prog=prog, args=args)
304 node_pos = {}
305 for n in G:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1483, in AGraph.layout(self, prog, args)
1466 """Assign positions to nodes in graph.
1467
1468 Optional prog=['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
(...)
1480
1481 """
1482 output_fmt = "dot"
-> 1483 data = self._run_prog(prog, " ".join([args, "-T", output_fmt]))
1484 self.from_string(data)
1485 self.has_layout = True
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1377, in AGraph._run_prog(self, prog, args)
1367 def _run_prog(self, prog="nop", args=""):
1368 """Apply graphviz program to graph and return the result as a string.
1369
1370 >>> import pygraphviz as pgv
(...)
1375 Use keyword args to add additional arguments to graphviz programs.
1376 """
-> 1377 runprog = r'"%s"' % self._get_prog(prog)
1378 cmd = " ".join([runprog, args])
1379 dotargs = shlex.split(cmd)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1363, in AGraph._get_prog(self, prog)
1361 runprog = self._which(prog)
1362 except:
-> 1363 raise ValueError(f"Program {prog} not found in path.")
1365 return runprog
ValueError: Program dot not found in path.
11.8. Causal Graph
The causal graph of a problem shows how the different (grounded) fluents of the problem are interwined by the actions.
Every node of the graph is a fluent and there is an arc from F1 to F2 if there is an action in the problem that reads F1 and writes F2. If an action writes both F1 and F2 the arc from F1 to F2 will be bidirectional.
The edge labels are the actions that use both fluents.
11.8.1. Define the problem
The problem has a cargo at Location l2 that must be moved by a robot to a location l1.
[11]:
# Define UserTypes
Location = UserType("Location")
# Define Fluents
robot_at = Fluent("robot_at", BoolType(), position=Location)
cargo_at = Fluent("cargo_at", BoolType(), position=Location)
cargo_mounted = Fluent("cargo_mounted")
# Define move action
move = InstantaneousAction("move", l_from=Location, l_to=Location)
l_from = move.parameter("l_from")
l_to = move.parameter("l_to")
move.add_precondition(robot_at(l_from))
move.add_precondition(Not(robot_at(l_to)))
move.add_effect(robot_at(l_from), False)
move.add_effect(robot_at(l_to), True)
# Define load action
load = InstantaneousAction("load", loc=Location)
loc = load.parameter("loc")
load.add_precondition(cargo_at(loc))
load.add_precondition(robot_at(loc))
load.add_effect(cargo_at(loc), False)
load.add_effect(cargo_mounted, True)
# Define unload action
unload = InstantaneousAction("unload", loc=Location)
loc = unload.parameter("loc")
unload.add_precondition(robot_at(loc))
unload.add_precondition(cargo_mounted)
unload.add_effect(cargo_at(loc), True)
unload.add_effect(cargo_mounted, False)
# Define objects
l1 = Object("l1", Location)
l2 = Object("l2", Location)
# Create the problem and add fluents, actions and objects
problem = Problem("robot_loader")
problem.add_fluent(robot_at, default_initial_value=False)
problem.add_fluent(cargo_at, default_initial_value=False)
problem.add_fluent(cargo_mounted, default_initial_value=False)
problem.add_action(move)
problem.add_action(load)
problem.add_action(unload)
problem.add_objects((l1, l2))
# Set initial value and goals
problem.set_initial_value(robot_at(l1), True)
problem.set_initial_value(cargo_at(l2), True)
problem.add_goal(cargo_at(l1))
Plot the causal graph
[12]:
plot_causal_graph(problem)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1361, in AGraph._get_prog(self, prog)
1360 try: # user must pick one of the graphviz programs...
-> 1361 runprog = self._which(prog)
1362 except:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1816, in AGraph._which(self, name)
1815 return match[0]
-> 1816 raise ValueError(f"No prog {name} in path.")
ValueError: No prog dot in path.
During handling of the above exception, another exception occurred:
ValueError Traceback (most recent call last)
Cell In[12], line 1
----> 1 plot_causal_graph(problem)
Cell In[3], line 20, in _function(original_function, *args, **kwargs)
16 original_function(*args, **kwargs,
17 filename=f"{os.path.join(tf.gettempdir(), str(uuid.uuid1()))}.png"
18 )
19 else:
---> 20 raise e
Cell In[3], line 13, in _function(original_function, *args, **kwargs)
11 def _function(original_function, *args, **kwargs):
12 try:
---> 13 original_function(*args, **kwargs)
14 except Exception as e:
15 if "could not locate runnable browser" in str(e):
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/causal_graph_plot.py:234, in plot_causal_graph(problem, filename, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, generate_edge_label, edge_font_size, edge_font_color, draw_networkx_kwargs, draw_networkx_edge_labels_kwargs)
225 label.add(
226 edge_label_function(
227 *actions_mapping.get(ln_action, (ln_action, tuple()))
228 )
229 )
230 edge_labels: Dict[Tuple[FNode, FNode], str] = {
231 edge: ", ".join(labels) for edge, labels in edge_labels_set.items() if labels
232 }
--> 234 fig, ax, pos = draw_base_graph(
235 graph,
236 figsize=figsize,
237 top_bottom=top_bottom,
238 generate_node_label=generate_node_label,
239 arrowsize=arrowsize,
240 node_size=node_size,
241 node_color=node_color,
242 edge_color=edge_color,
243 font_size=font_size,
244 font_color=font_color,
245 draw_networkx_kwargs=draw_networkx_kwargs,
246 )
247 nx.draw_networkx_edge_labels(
248 graph,
249 pos,
(...)
254 **draw_networkx_edge_labels_kwargs,
255 )
256 if filename is None:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:95, in draw_base_graph(graph, figsize, top_bottom, generate_node_label, arrowsize, node_size, node_color, edge_color, font_size, font_color, draw_networkx_kwargs)
92 fig = plt.figure(figsize=figsize)
93 ax = fig.add_subplot()
---> 95 pos = _generate_positions(graph, prog="dot", top_bottom=top_bottom)
97 nx.draw_networkx(
98 graph,
99 pos,
(...)
110 **draw_networkx_kwargs,
111 )
112 return fig, ax, pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/unified_planning/plot/utils.py:139, in _generate_positions(graph, prog, top_bottom)
136 else:
137 new_graph.graph.setdefault("graph", {})["rankdir"] = "LR"
--> 139 new_pos = nx.nx_agraph.graphviz_layout(new_graph, prog=prog)
141 pos = {id_to_node[i]: value for i, value in new_pos.items()}
143 return pos
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:251, in graphviz_layout(G, prog, root, args)
220 def graphviz_layout(G, prog="neato", root=None, args=""):
221 """Create node positions for G using Graphviz.
222
223 Parameters
(...)
249 see https://gitlab.com/graphviz/graphviz/-/issues/1767 for more info.
250 """
--> 251 return pygraphviz_layout(G, prog=prog, root=root, args=args)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/networkx/drawing/nx_agraph.py:303, in pygraphviz_layout(G, prog, root, args)
301 args += f"-Groot={root}"
302 A = to_agraph(G)
--> 303 A.layout(prog=prog, args=args)
304 node_pos = {}
305 for n in G:
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1483, in AGraph.layout(self, prog, args)
1466 """Assign positions to nodes in graph.
1467
1468 Optional prog=['neato'|'dot'|'twopi'|'circo'|'fdp'|'nop']
(...)
1480
1481 """
1482 output_fmt = "dot"
-> 1483 data = self._run_prog(prog, " ".join([args, "-T", output_fmt]))
1484 self.from_string(data)
1485 self.has_layout = True
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1377, in AGraph._run_prog(self, prog, args)
1367 def _run_prog(self, prog="nop", args=""):
1368 """Apply graphviz program to graph and return the result as a string.
1369
1370 >>> import pygraphviz as pgv
(...)
1375 Use keyword args to add additional arguments to graphviz programs.
1376 """
-> 1377 runprog = r'"%s"' % self._get_prog(prog)
1378 cmd = " ".join([runprog, args])
1379 dotargs = shlex.split(cmd)
File ~/checkouts/readthedocs.org/user_builds/unified-planning/envs/v1.0.0/lib/python3.10/site-packages/pygraphviz/agraph.py:1363, in AGraph._get_prog(self, prog)
1361 runprog = self._which(prog)
1362 except:
-> 1363 raise ValueError(f"Program {prog} not found in path.")
1365 return runprog
ValueError: Program dot not found in path.