Repository URL to install this package:
|
Version:
0.12.2 ▾
|
# -*- coding: utf-8 -*-
# This code is part of Qiskit.
#
# (C) Copyright IBM 2018, 2019, 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=invalid-name
"""Parser for the string specification of Hamiltonians and noise models"""
import re
import copy
from collections import namedtuple, OrderedDict
import numpy as np
from qiskit.quantum_info import Operator
from .apply_str_func_to_qobj import apply_func
from .operator_from_string import gen_oper
Token = namedtuple("Token", ("type", "name"))
ham_elements = OrderedDict(
QubOpr=re.compile(r"(?P<opr>O|Sp|Sm|X|Y|Z|I)(?P<idx>[0-9]+)"),
PrjOpr=re.compile(r"P(?P<idx>[0-9]+),(?P<ket>[0-9]+),(?P<bra>[0-9]+)"),
CavOpr=re.compile(r"(?P<opr>A|C|N)(?P<idx>[0-9]+)"),
Func=re.compile(r"(?P<name>[a-z]+)\("),
Ext=re.compile(r"\.(?P<name>dag)"),
Var=re.compile(r"[a-z]+[0-9]*"),
Num=re.compile(r"[0-9.]+"),
MathOrd0=re.compile(r"[*/]"),
MathOrd1=re.compile(r"[+-]"),
BrkL=re.compile(r"\("),
BrkR=re.compile(r"\)"),
)
class HamiltonianParser:
"""Generate QuTip hamiltonian object from string"""
def __init__(self, h_str, dim_osc, dim_qub):
"""Create new quantum operator generator
Parameters:
h_str (list): list of Hamiltonian string
dim_osc (dict): dimension of oscillator subspace
dim_qub (dict): dimension of qubit subspace
"""
self.h_str = h_str
self.dim_osc = dim_osc
self.dim_qub = dim_qub
self.__td_hams = []
self.__tc_hams = []
self.__str2qopr = {}
@property
def compiled(self):
"""Return Hamiltonian in OpenPulse handler format"""
return self.__tc_hams + self.__td_hams
def parse(self, qubit_list=None):
"""Parse and generate quantum class object"""
self.__td_hams = []
self.__tc_hams = []
# expand sum
self._expand_sum()
# convert to reverse Polish notation
for ham in self.h_str:
if len(re.findall(r"\|\|", ham)) > 1:
raise Exception("Multiple time-dependent terms in %s" % ham)
p_td = re.search(r"(?P<opr>[\S]+)\|\|(?P<ch>[\S]+)", ham)
# find time-dependent term
if p_td:
coef, token = self._tokenizer(p_td.group("opr"), qubit_list)
if token is None:
continue
# combine coefficient to time-dependent term
if coef:
td = "*".join([coef, p_td.group("ch")])
else:
td = p_td.group("ch")
token = self._shunting_yard(token)
_td = self._token2qobj(token), td
self.__td_hams.append(_td)
else:
coef, token = self._tokenizer(ham, qubit_list)
if token is None:
continue
token = self._shunting_yard(token)
if (coef == "") or (coef is None):
coef = "1."
_tc = self._token2qobj(token), coef
self.__tc_hams.append(_tc)
def _expand_sum(self):
"""Takes a string-based Hamiltonian list and expands the _SUM action items out."""
sum_str = re.compile(r"_SUM\[(?P<itr>[a-z]),(?P<l>[a-z\d{}+-]+),(?P<u>[a-z\d{}+-]+),")
brk_str = re.compile(r"]")
ham_list = copy.copy(self.h_str)
ham_out = []
while any(ham_list):
ham = ham_list.pop(0)
p_sums = list(sum_str.finditer(ham))
p_brks = list(brk_str.finditer(ham))
if len(p_sums) != len(p_brks):
raise Exception("Missing correct number of brackets in %s" % ham)
# find correct sum-bracket correspondence
if any(p_sums) == 0:
ham_out.append(ham)
else:
itr = p_sums[0].group("itr")
_l = int(p_sums[0].group("l"))
_u = int(p_sums[0].group("u"))
for ii in range(len(p_sums) - 1):
if p_sums[ii + 1].end() > p_brks[ii].start():
break
else:
ii = len(p_sums) - 1
# substitute iterator value
_temp = []
for kk in range(_l, _u + 1):
trg_s = ham[p_sums[0].end() : p_brks[ii].start()]
# generate replacement pattern
pattern = {}
for p in re.finditer(r"\{(?P<op_str>[a-z0-9*/+-]+)\}", trg_s):
if p.group() not in pattern:
sub = parse_binop(p.group("op_str"), operands={itr: str(kk)})
if sub.isdecimal():
pattern[p.group()] = sub
else:
pattern[p.group()] = "{%s}" % sub
for key, val in pattern.items():
trg_s = trg_s.replace(key, val)
_temp.append(
"".join([ham[: p_sums[0].start()], trg_s, ham[p_brks[ii].end() :]])
)
ham_list.extend(_temp)
self.h_str = ham_out
return ham_out
def _tokenizer(self, op_str, qubit_list=None):
"""Convert string to token and coefficient
Check if the index is in qubit_list
"""
# generate token
_op_str = copy.copy(op_str)
token_list = []
prev = "none"
while any(_op_str):
for key, parser in ham_elements.items():
p = parser.match(_op_str)
if p:
# find quantum operators
if key in ["QubOpr", "CavOpr"]:
_key = key
_name = p.group()
if p.group() not in self.__str2qopr.keys():
idx = int(p.group("idx"))
if qubit_list is not None and idx not in qubit_list:
return 0, None
name = p.group("opr")
opr = gen_oper(name, idx, self.dim_osc, self.dim_qub)
self.__str2qopr[p.group()] = opr
elif key == "PrjOpr":
_key = key
_name = p.group()
if p.group() not in self.__str2qopr.keys():
idx = int(p.group("idx"))
name = "P"
lvs = int(p.group("ket")), int(p.group("bra"))
opr = gen_oper(name, idx, self.dim_osc, self.dim_qub, lvs)
self.__str2qopr[p.group()] = opr
elif key in ["Func", "Ext"]:
_name = p.group("name")
_key = key
elif key == "MathOrd1":
_name = p.group()
if prev not in ["QubOpr", "PrjOpr", "CavOpr", "Var", "Num"]:
_key = "MathUnitary"
else:
_key = key
else:
_name = p.group()
_key = key
token_list.append(Token(_key, _name))
_op_str = _op_str[p.end() :]
prev = _key
break
else:
raise Exception("Invalid input string %s is found" % op_str)
# split coefficient
coef = ""
if any([k.type == "Var" for k in token_list]):
for ii, _ in enumerate(token_list):
if token_list[ii].name == "*":
if all([k.type != "Var" for k in token_list[ii + 1 :]]):
coef = "".join([k.name for k in token_list[:ii]])
token_list = token_list[ii + 1 :]
break
else:
raise Exception("Invalid order of operators and coefficients in %s" % op_str)
return coef, token_list
def _shunting_yard(self, token_list):
"""Reformat token to reverse Polish notation"""
stack = []
queue = []
while any(token_list):
token = token_list.pop(0)
if token.type in ["QubOpr", "PrjOpr", "CavOpr", "Num"]:
queue.append(token)
elif token.type in ["Func", "Ext"]:
stack.append(token)
elif token.type in ["MathUnitary", "MathOrd0", "MathOrd1"]:
while stack and math_priority(token, stack[-1]):
queue.append(stack.pop(-1))
stack.append(token)
elif token.type in ["BrkL"]:
stack.append(token)
elif token.type in ["BrkR"]:
while stack[-1].type not in ["BrkL", "Func"]:
queue.append(stack.pop(-1))
if not any(stack):
raise Exception("Missing correct number of brackets")
pop = stack.pop(-1)
if pop.type == "Func":
queue.append(pop)
else:
raise Exception("Invalid token %s is found" % token.name)
while any(stack):
queue.append(stack.pop(-1))
return queue
def _token2qobj(self, tokens):
"""Generate quantum class object from tokens"""
stack = []
for token in tokens:
if token.type in ["QubOpr", "PrjOpr", "CavOpr"]:
stack.append(self.__str2qopr[token.name])
elif token.type == "Num":
stack.append(float(token.name))
elif token.type in ["MathUnitary"]:
if token.name == "-":
stack.append(-stack.pop(-1))
elif token.type in ["MathOrd0", "MathOrd1"]:
op2 = stack.pop(-1)
op1 = stack.pop(-1)
if token.name == "+":
stack.append(op1 + op2)
elif token.name == "-":
stack.append(op1 - op2)
elif token.name == "*":
if isinstance(op1, Operator) and isinstance(op2, Operator):
stack.append(op1 & op2)
else:
stack.append(op1 * op2)
elif token.name == "/":
stack.append(op1 / op2)
elif token.type in ["Func", "Ext"]:
stack.append(apply_func(token.name, stack.pop(-1)))
else:
raise Exception("Invalid token %s is found" % token.name)
if len(stack) > 1:
raise Exception("Invalid mathematical operation in " % tokens)
return stack[0]
class NoiseParser:
"""Generate QuTip noise object from dictionary
Qubit noise is given in the format of nested dictionary:
"qubit": {
"0": {
"Sm": 0.006
}
}
and oscillator noise is given in the format of nested dictionary:
"oscillator": {
"n_th": {
"0": 0.001
},
"coupling": {
"0": 0.05
}
}
these configurations are combined in the same dictionary
"""
def __init__(self, noise_dict, dim_osc, dim_qub):
"""Create new quantum operator generator
Parameters:
noise_dict (dict): dictionary of noise configuration
dim_osc (dict): dimension of oscillator subspace
dim_qub (dict): dimension of qubit subspace
"""
self.noise_osc = noise_dict.get("oscillator", {"n_th": {}, "coupling": {}})
self.noise_qub = noise_dict.get("qubit", {})
self.dim_osc = dim_osc
self.dim_qub = dim_qub
self.__c_list = []
@property
def compiled(self):
"""Return noise configuration in OpenPulse handler format"""
return self.__c_list
def parse(self):
"""Parse and generate quantum class object"""
# Qubit noise
for index, config in self.noise_qub.items():
for opname, coef in config.items():
# TODO: support noise in multi-dimensional system
# TODO: support noise with math operation
if opname in ["X", "Y", "Z", "Sp", "Sm"]:
opr = gen_oper(opname, int(index), self.dim_osc, self.dim_qub)
else:
raise Exception("Unsupported noise operator %s is given" % opname)
self.__c_list.append(np.sqrt(coef) * opr)
# Oscillator noise
ndic = self.noise_osc["n_th"]
cdic = self.noise_osc["coupling"]
for (n_ii, n_coef), (c_ii, c_coef) in zip(ndic.items(), cdic.items()):
if n_ii == c_ii:
if c_coef > 0:
opr = gen_oper("A", int(n_ii), self.dim_osc, self.dim_qub)
if n_coef:
self.__c_list.append(np.sqrt(c_coef * (1 + n_coef)) * opr)
self.__c_list.append(np.sqrt(c_coef * n_coef) * opr.dag())
else:
self.__c_list.append(np.sqrt(c_coef) * opr)
else:
raise Exception("Invalid oscillator index in noise dictionary.")
def math_priority(o1, o2):
"""Check priority of given math operation"""
rank = {"MathUnitary": 2, "MathOrd0": 1, "MathOrd1": 0}
diff_ops = rank.get(o1.type, -1) - rank.get(o2.type, -1)
if diff_ops > 0:
return False
else:
return True
# pylint: disable=dangerous-default-value
def parse_binop(op_str, operands={}, cast_str=True):
"""Calculate binary operation in string format"""
oprs = OrderedDict(
sum=r"(?P<v0>[a-zA-Z0-9]+)\+(?P<v1>[a-zA-Z0-9]+)",
sub=r"(?P<v0>[a-zA-Z0-9]+)\-(?P<v1>[a-zA-Z0-9]+)",
mul=r"(?P<v0>[a-zA-Z0-9]+)\*(?P<v1>[a-zA-Z0-9]+)",
div=r"(?P<v0>[a-zA-Z0-9]+)\/(?P<v1>[a-zA-Z0-9]+)",
non=r"(?P<v0>[a-zA-Z0-9]+)",
)
for key, regr in oprs.items():
p = re.match(regr, op_str)
if p:
val0 = operands.get(p.group("v0"), p.group("v0"))
if key == "non":
# substitution
retv = val0
else:
val1 = operands.get(p.group("v1"), p.group("v1"))
# binary operation
if key == "sum":
if val0.isdecimal() and val1.isdecimal():
retv = int(val0) + int(val1)
else:
retv = "+".join([str(val0), str(val1)])
elif key == "sub":
if val0.isdecimal() and val1.isdecimal():
retv = int(val0) - int(val1)
else:
retv = "-".join([str(val0), str(val1)])
elif key == "mul":
if val0.isdecimal() and val1.isdecimal():
retv = int(val0) * int(val1)
else:
retv = "*".join([str(val0), str(val1)])
elif key == "div":
if val0.isdecimal() and val1.isdecimal():
retv = int(val0) / int(val1)
else:
retv = "/".join([str(val0), str(val1)])
else:
retv = 0
break
else:
raise Exception("Invalid string %s" % op_str)
if cast_str:
return str(retv)
else:
return retv