{
"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-hamiltonian-parameter-estimation-using-a-small-amount-of-measured-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](https://docs.q-ctrl.com/boulder-opal/references/qctrl/Graphs.html#graph-operations).\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 Python graph object:\n",
"```python\n",
"graph = qctrl.create_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/boulder-opal/references/qctrl/Graphs.html#graph-operations) to your graph.\n",
"For optimization problems, this includes using the `graph.optimization_variable` operation to define the 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/boulder-opal/references/qctrl/Functions.html), such as `qctrl.functions.calculate_graph` or `qctrl.functions.calculate_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 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` or `best_cost` attribute of the result.\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",
"from qctrl import Qctrl\n",
"\n",
"# Start a Boulder Opal session.\n",
"qctrl = Qctrl()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# Create graph.\n",
"\n",
"graph = qctrl.create_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": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
" 0%| | 0/100 [00:00