# Copyright 2019 Xanadu Quantum Technologies Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=too-many-return-statements,too-many-branches,too-many-instance-attributes
"""
.. _utils:
Utilities
=========
**Module name:** `blackbird.utils`
.. currentmodule:: blackbird.utils
This module contains utility functions for Blackbird programs,
including generating directed acyclic graphs, and pattern matching
and Blackbird programs and templates.
Summary
-------
.. autosummary::
TemplateError
to_DiGraph
match_template
Code details
~~~~~~~~~~~~
"""
from collections import namedtuple
import numpy as np
import sympy as sym
from sympy.solvers import solve
import networkx as nx
from networkx.algorithms import isomorphism
from .listener import RegRefTransform
Command = namedtuple('Command', ['name', 'args', 'kwargs', 'modes'])
"""namedtuple: encapsulate a specific quantum operation"""
[docs]class TemplateError(Exception):
"""Exception class for template related errors"""
pass
[docs]def to_DiGraph(program):
"""Convert a Blackbird program to a directed acyclic graph.
The resulting graph has nodes representing quantum operations,
and edges representing dependent/successor operations.
Each node is labelled by an integer; note attributes are used
to store information about the node:
* ``'name'`` *(str)*: name of the quantum operation (e.g., ``'S2gate'``)
* ``'args'`` *(list)*: positional arguments of the operation
* ``'kwargs'`` *(dict)*: keyword arguments of the operation
* ``'modes'`` *(tuple[int])*: modes the operation acts on
Args:
program (blackbird.Program): a Blackbird program
Returns:
networkx.DiGraph: the directed acyclic graph representing
the quantum program
"""
grid = {}
for idx, op in enumerate(program.operations):
dependencies = set(op['modes'])
if 'args' in op:
for a in op['args']:
if isinstance(a, RegRefTransform):
dependencies |= set(a.regrefs)
for _, v in op['kwargs'].items():
if isinstance(v, RegRefTransform):
dependencies |= set(v.regrefs)
else:
op['args'] = []
op['kwargs'] = {}
cmd = Command(name=op['op'], args=op['args'], kwargs=op['kwargs'], modes=tuple(op['modes']))
for q in dependencies:
# Add cmd to the grid to the end of the line r.ind.
if q not in grid:
# add a new line to the circuit
grid[q] = []
grid[q].append([idx, cmd])
G = nx.DiGraph()
for q, cmds in grid.items():
if cmds:
# add the first operation on the wire that does not depend on anything
attrs = cmds[0][1]._asdict()
G.add_node(cmds[0][0], **attrs)
for i in range(1, len(cmds)):
# add the edge between the operations, and the operation nodes themselves
if cmds[i][0] not in G:
attrs = cmds[i][1]._asdict()
G.add_node(cmds[i][0], **attrs)
G.add_edge(cmds[i-1][0], cmds[i][0])
return G
[docs]def match_template(template, program):
"""Match a template against a Blackbird program, returning template
parameter values.
For example, consider the following template and program:
.. code-block:: python
template = blackbird.loads(\"\"\"\\
name prog
version 1.0
Dgate(-{r}, 0.45) | 1
Vac | 2
Sgate({r}, 2*{phi}-1) | 0
\"\"\")
program = blackbird.loads(\"\"\"\\
name prog
version 1.0
Sgate(0.543, -1.432*pi) | 0
Dgate(-0.543, 0.45) | 1
Vac | 2
\"\"\")
By applying the ``match_template`` function, we can match the template parameters:
>>> res = match_template(template, program)
>>> print(res)
{'r': 0.543, 'phi': -1.74938033997029}
Verifying this is correct:
>>> print((-1.432*np.pi+1)/2)
-1.7493803399702919
.. note::
The template and the Blackbird program to match must have the *same*
version number and target, otherwise an :class:`TemplateError` will be raised.
Args:
template (blackbird.Program): the Blackbird template
program (blackbird.Program): a Blackbird program to match against
the template
Returns:
dict[str, Number]: mapping from the template parameter name
to a numerical value.
"""
# check template
if not template.is_template():
raise TemplateError("Argument 1 is not a template.")
if program.is_template():
raise TemplateError("Argument 2 cannot be a template.")
if template.version != program.version:
raise TemplateError("Mismatching Blackbird version between template and program")
if template.target['name'] != program.target['name']:
raise TemplateError("Mismatching target between template and program")
G1 = to_DiGraph(template)
G2 = to_DiGraph(program)
def node_match(n1, n2):
"""Returns True if both nodes have the same name"""
return n1['name'] == n2['name'] and n1['modes'] == n2['modes']
GM = isomorphism.DiGraphMatcher(G1, G2, node_match)
# check if topology matches
if not GM.is_isomorphic():
raise TemplateError("Not the same program.")
G1nodes = G1.nodes().data()
G2nodes = G2.nodes().data()
argmatch = {}
key = ""
for n1, n2 in GM.mapping.items():
for x, y in zip(G1nodes[n1]['args'], G2nodes[n2]['args']):
if np.all(x != y):
if isinstance(x, sym.Symbol):
key = str(x)
val = y
elif isinstance(x, sym.Expr):
# need to symbolically solve for the symbol
var = x.free_symbols
if len(var) > 1:
raise TemplateError("Matching template parameters only supports "
"one template parameter per gate argument.")
res = solve(x-y, var)
key = str(var)[1:-1]
val = float(res[-1])
if key in argmatch:
if argmatch[key] != val:
raise TemplateError("Template parameter {} matches inconsistent values: "
"{} and {}".format(key, val, argmatch[key]))
if key != "":
argmatch[key] = val
p_params = {
k: program.variables[str(v)] for k, v in argmatch.items() if str(v) in program.variables
}
argmatch.update(p_params)
return argmatch