{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# How to calculate and optimize with graphs\n",
"**Create graphs for computations with Boulder Opal**\n",
"\n",
"Graphs are a very efficient way of describing computations using a combination of *nodes* and *edges*. Please refer to our topic [Understanding graphs in Boulder Opal](https://docs.q-ctrl.com/boulder-opal/topics/) for an introduction to what graphs are used for and why.\n",
"You can also review our [tutorials](https://docs.q-ctrl.com/boulder-opal/tutorials/) and [user guides](https://docs.q-ctrl.com/boulder-opal/user-guides/) with examples on how to use graph representations for [robust control](https://docs.q-ctrl.com/boulder-opal/tutorials/design-robust-single-qubit-gates-using-computational-graphs) (for calculating optimized control pulses), [simulation](https://docs.q-ctrl.com/boulder-opal/tutorials/simulate-the-dynamics-of-a-single-qubit-using-computational-graphs) (to understand the dynamics of the system in the presence of specific controls and noises), and [system identification](https://docs.q-ctrl.com/boulder-opal/user-guides/how-to-perform-parameter-estimation-with-a-small-amount-of-data) (to estimate the values of unknown system parameters based on measurements of the system).\n",
"You can find a list of all predefined graph operations in Boulder Opal in our [reference documentation](https://docs.q-ctrl.com/references/boulder-opal/boulderopal/graph/nodes.html).\n",
"\n",
"Here we will show how to use graphs to perform simple calculations and optimizations."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary workflow\n",
"\n",
"When working with graphs, we typically follow the following steps.\n",
"\n",
"### 1. Create an empty graph\n",
"\n",
"You always start by creating an empty Boulder Opal graph object:\n",
"```python\n",
"graph = bo.Graph()\n",
"```\n",
"\n",
"### 2. Add the appropriate nodes and operations\n",
"\n",
"Depending on the task at hand, you can add the appropriate [nodes and operations](https://docs.q-ctrl.com/references/boulder-opal/boulderopal/graph/nodes.html) to your graph.\n",
"For optimization problems, this includes using [operations that create optimization variables](https://docs.q-ctrl.com/references/boulder-opal/boulderopal/graph/nodes.html#optimization-variables) defining those variables whose values you want to optimize.\n",
"\n",
"### 3. Evaluate the graph\n",
"\n",
"To execute the sequence of operations represented by the graph, you use one of our [graph evaluation functions](https://docs.q-ctrl.com/references/boulder-opal/boulderopal/functions.html), such as `boulderopal.execute_graph` or `boulderopal.run_optimization`.\n",
"When calling these functions, besides passing in the graph, you need to provide a list of `output_node_names` with the strings corresponding to the nodes (or a single node name) whose values you want to extract from the graph.\n",
"For optimization problems, you also use the `cost_node_name` parameter to point to the optimization function previously defined.\n",
"\n",
"### 4. Extract the results\n",
"\n",
"You can now access the node results that you requested in the previous step. They are given in the form of a dictionary with entries for each output node name you provided.\n",
"For optimization problems, the best value achieved by the cost function is returned separately in the `cost` key of the result dictionary.\n",
"\n",
"## Example: Calculating a simple graph\n",
"\n",
"In this example, we will execute a simple graph to calculate the trace of a matrix:\n",
"$$\n",
"\\mathrm{tr} \\left[ \\sigma_z \\otimes \\sigma_z + \\mathrm{Id}_4 \\right] ,\n",
"$$\n",
"where $\\sigma_z$ is the Pauli Z operator and $\\mathrm{Id}_4$ the $4\\times 4$ identity matrix.\n",
"\n",
"We will build a graph that carries out this computation, defining matrices $A = \\sigma_z \\otimes \\sigma_z$ and $B = A + \\mathrm{Id}_4$.\n",
"For each node whose values we want to extract, we will assign a name to it by passing a `name` keyword argument to the operation that creates it, or by manually changing their `name` attribute (for instance if the node is created by applying regular Python arithmetic operators to other nodes)."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import boulderopal as bo"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# Create graph.\n",
"graph = bo.Graph()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# Add nodes and operations.\n",
"identity_4 = np.eye(4)\n",
"\n",
"# Create node with matrix A.\n",
"# (We don't need to assign a name to it as we don't want to extract its value).\n",
"matrix_a = graph.pauli_kronecker_product([(\"Z\", 0), (\"Z\", 1)], 2)\n",
"\n",
"# Create node with matrix B.\n",
"matrix_b = matrix_a + identity_4\n",
"matrix_b.name = \"matrix\"\n",
"\n",
"# Create node calculating the trace.\n",
"trace = graph.trace(matrix_b, name=\"trace\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Your task (action_id=\"1829403\") has completed.\n"
]
}
],
"source": [
"# Execute the graph.\n",
"result = bo.execute_graph(graph=graph, output_node_names=[\"matrix\", \"trace\"])"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Keys of result['output']:\n",
" ['trace', 'matrix']\n",
"\n",
"Matrix value:\n",
" [[2.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
" [0.+0.j 0.+0.j 0.+0.j 2.+0.j]]\n",
"\n",
"Trace value:\n",
" (4+0j)\n"
]
}
],
"source": [
"# Extract results.\n",
"print(f\"Keys of result['output']:\\n {list(result['output'].keys())}\\n\")\n",
"print(f\"Matrix value:\\n {result['output']['matrix']['value']}\\n\")\n",
"print(f\"Trace value:\\n {result['output']['trace']['value']}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example: Performing a simple optimization\n",
"\n",
"In this example, we will execute a simple graph to find the minimum of the Booth function,\n",
"$$\n",
"f(x,y) = (x + 2 y - 7)^2 + (2 x + y - 5)^2 ,\n",
"$$\n",
"which has a global minimum at $f(x=1,y=3) = 0$."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Create graph.\n",
"graph = bo.Graph()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Add nodes and operations.\n",
"\n",
"# Create optimization variables.\n",
"x = graph.optimizable_scalar(lower_bound=-10, upper_bound=10, name=\"x\")\n",
"y = graph.optimizable_scalar(lower_bound=-10, upper_bound=10, name=\"y\")\n",
"\n",
"# Create cost node.\n",
"cost = (x + 2 * y - 7) ** 2 + (2 * x + y - 5) ** 2\n",
"cost.name = \"cost\""
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Your task (action_id=\"1829404\") has started.\n",
"Your task (action_id=\"1829404\") has completed.\n"
]
}
],
"source": [
"# Execute the graph, miniizing the value of the cost node.\n",
"result = bo.run_optimization(\n",
" graph=graph,\n",
" cost_node_name=\"cost\",\n",
" output_node_names=[\"x\", \"y\"],\n",
" optimization_count=4,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Optimized cost = 2.524e-29\n",
"\n",
"Optimization variables (x, y) = (0.9999999999999964, 3.0000000000000036)\n"
]
}
],
"source": [
"# Extract results.\n",
"\n",
"# Inspect the value reached by the cost function.\n",
"print(f\"Optimized cost = {result['cost']:.3e}\")\n",
"\n",
"# Inspect the values of the output nodes.\n",
"print(\n",
" f\"\\nOptimization variables (x, y) = {(result['output']['x']['value'], result['output']['y']['value'])}\"\n",
")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.4"
}
},
"nbformat": 4,
"nbformat_minor": 4
}