Skip to content

Density matrix class and simulation #324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/tequila/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from tequila.optimizers import INSTALLED_OPTIMIZERS, show_available_optimizers
from tequila.optimizers import minimize, minimize_scipy, minimize_gd, optimizer_scipy

from tequila.simulators.simulator_api import simulate, compile, compile_to_function, draw, pick_backend, \
from tequila.simulators.simulator_api import simulate, simulate_density, compile, compile_to_function, draw, pick_backend, \
INSTALLED_SAMPLERS, \
INSTALLED_SIMULATORS, SUPPORTED_BACKENDS, INSTALLED_BACKENDS, show_available_simulators
from tequila.wavefunction import QubitWaveFunction
Expand Down
3 changes: 3 additions & 0 deletions src/tequila/hamiltonian/qubit_hamiltonian.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,3 +666,6 @@ def is_all_z(self):
if not p.is_all_z():
return False
return True

def constant(self):
return self._qubit_operator.constant
46 changes: 41 additions & 5 deletions src/tequila/simulators/simulator_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@
from tequila.simulators.simulator_base import BackendCircuit, BackendExpectationValue
from tequila.circuit.noise import NoiseModel
from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction
from tequila.wavefunction.density_matrix import DensityMatrix

SUPPORTED_BACKENDS = ["qulacs", "qulacs_gpu", "qibo", "qiskit", "qiskit_gpu", "cirq", "pyquil", "symbolic", "qlm", "spex"]
SUPPORTED_DENSITY_BACKENDS = ["qiskit"]
# TODO: Reenable noise for Qiskit
SUPPORTED_NOISE_BACKENDS = ["cirq", "pyquil"] # qulacs removed in v.1.9

BackendTypes = namedtuple('BackendTypes', 'CircType ExpValueType')
INSTALLED_SIMULATORS = {}
INSTALLED_SAMPLERS = {}
Expand Down Expand Up @@ -163,7 +166,7 @@ def show_available_simulators():
print("missing qiskit_aer: no noisy simulation")


def pick_backend(backend: str = None, samples: int = None, noise: NoiseModel = None, device=None,
def pick_backend(backend: str = None, samples: int = None, simulate_density: bool = False, noise: NoiseModel = None, device=None,
exclude_symbolic: bool = True) -> str:

"""
Expand Down Expand Up @@ -195,6 +198,13 @@ def pick_backend(backend: str = None, samples: int = None, noise: NoiseModel = N
raise TequilaException('device use requires backend specification!')

if backend is None:
if simulate_density:
for f in SUPPORTED_DENSITY_BACKENDS:
if f in INSTALLED_SIMULATORS:
return f
raise TequilaException(
"Density simulators unavailable!")

if noise is None:
if samples is None:
for f in SUPPORTED_BACKENDS:
Expand Down Expand Up @@ -250,6 +260,7 @@ def compile_objective(objective: typing.Union['Objective'],
variables: typing.Dict['Variable', 'RealNumber'] = None,
backend: str = None,
samples: int = None,
simulate_density: bool = False,
device: str = None,
noise: NoiseModel = None,
*args,
Expand Down Expand Up @@ -279,7 +290,7 @@ def compile_objective(objective: typing.Union['Objective'],
the compiled objective.
"""

backend = pick_backend(backend=backend, samples=samples, noise=noise, device=device)
backend = pick_backend(backend=backend, samples=samples, simulate_density=simulate_density, noise=noise, device=device)

# dummy variables
if variables is None:
Expand Down Expand Up @@ -325,6 +336,7 @@ def compile_circuit(abstract_circuit: 'QCircuit',
variables: typing.Dict['Variable', 'RealNumber'] = None,
backend: str = None,
samples: int = None,
simulate_density: bool = False,
noise: NoiseModel = None,
device: str = None,
*args,
Expand Down Expand Up @@ -419,11 +431,34 @@ def simulate(objective: typing.Union['Objective', 'QCircuit', 'QTensor'],
"You called simulate for a parametrized type but forgot to pass down the variables: {}".format(
objective.extract_variables()))

compiled_objective = compile(objective=objective, samples=samples, variables=variables, backend=backend,
noise=noise, device=device, *args, **kwargs)
compiled_objective = compile(objective=objective, samples=samples, simulate_density=False, variables=variables, backend=backend,
noise=noise,device=device, *args, **kwargs)

return compiled_objective(variables=variables, samples=samples, initial_state=initial_state, *args, **kwargs)

def simulate_density(objective: typing.Union['Objective', 'QCircuit','QTensor'],
variables: Dict[Union[Variable, Hashable], RealNumber] = None,
backend: str = None,
noise: NoiseModel = None,
device: str = None,
*args,
**kwargs) -> Union[RealNumber, DensityMatrix]:

variables = format_variable_dictionary(variables)

if variables is None and not (len(objective.extract_variables()) == 0):
raise TequilaException(
"You called simulate for a parametrized type but forgot to pass down the variables: {}".format(
objective.extract_variables()))

if backend == None:
backend = 'qiskit' #currently only permissible backend!
elif backend.lower() != 'qiskit':
TequilaException("Density matrix simulation currently works with only qiskit backend!")

compiled_objective = compile(objective=objective, samples=None, simulate_density=True, variables=variables, backend=backend,
noise=noise,device=device, *args, **kwargs)
return compiled_objective(variables=variables, samples=None, simulate_density=True, *args, **kwargs)

def draw(objective, variables=None, backend: str = None, name=None, *args, **kwargs):
"""
Expand Down Expand Up @@ -515,6 +550,7 @@ def draw(objective, variables=None, backend: str = None, name=None, *args, **kwa
def compile(objective: typing.Union['Objective', 'QCircuit', 'QTensor'],
variables: Dict[Union['Variable', Hashable], RealNumber] = None,
samples: int = None,
simulate_density: bool = False,
backend: str = None,
noise: NoiseModel = None,
device: str = None,
Expand Down Expand Up @@ -544,7 +580,7 @@ def compile(objective: typing.Union['Objective', 'QCircuit', 'QTensor'],

"""

backend = pick_backend(backend=backend, noise=noise, samples=samples, device=device)
backend = pick_backend(backend=backend, noise=noise, samples=samples, simulate_density=simulate_density, device=device)

if variables is not None:
# allow hashable types as keys without casting it to variables
Expand Down
70 changes: 66 additions & 4 deletions src/tequila/simulators/simulator_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from tequila.utils.keymap import KeyMapSubregisterToRegister
from tequila.utils.misc import to_float
from tequila.wavefunction.qubit_wavefunction import QubitWaveFunction
from tequila.wavefunction.density_matrix import DensityMatrix
from tequila.circuit.compiler import change_basis
from tequila import BitString
from tequila.objective.objective import Variable, format_variable_dictionary
Expand Down Expand Up @@ -208,6 +209,7 @@ def __init__(self, abstract_circuit: QCircuit, variables, noise=None, device=Non
def __call__(self,
variables: typing.Dict[Variable, numbers.Real] = None,
samples: int = None,
simulate_density: bool = False,
*args,
**kwargs):
"""
Expand Down Expand Up @@ -236,6 +238,9 @@ def __call__(self,
self._variables, variables))

self.update_variables(variables)
if simulate_density:
return self.simulate_density(variables=variables, noise=self.noise, *args, **kwargs)

if samples is None:
return self.simulate(variables=variables, noise=self.noise, *args, **kwargs)
else:
Expand Down Expand Up @@ -390,6 +395,44 @@ def simulate(self, variables, initial_state: Union[int, QubitWaveFunction] = 0,

return result

def simulate_density(self, variables, initial_state=0, *args, **kwargs) -> DensityMatrix:
"""
simulate the circuit via the backend.

Parameters
----------
variables:
the parameters with which to simulate the circuit.
initial_state: Default = 0:
one of several types; determines the base state onto which the circuit is applied.
Default: the circuit is applied to the all-zero state.
args
kwargs

Returns
-------
DensityMatrix
the density of the system produced by the action of the circuit on the initial state and noise, if provided.
"""
self.update_variables(variables)
if isinstance(initial_state, BitString):
initial_state = initial_state.integer
if isinstance(initial_state, QubitWaveFunction):
if len(initial_state.keys()) != 1:
raise TequilaException("only product states as initial states accepted as of now") # TODO: add initial density state for simulation. Can use qiskit quantum info .DensityMatrix.evolve
initial_state = list(initial_state.keys())[0].integer

all_qubits = [i for i in range(self.abstract_circuit.n_qubits)]
active_qubits = self.qubit_map.keys()

# maps from reduced register to full register
keymap = KeyMapSubregisterToRegister(subregister=active_qubits, register=all_qubits)

result = self.do_simulate_density(variables=variables, initial_state=keymap.inverted(initial_state).integer, *args,
**kwargs)
result.apply_keymap(keymap=keymap, initial_state=initial_state)
return result

def sample(self, variables, samples, read_out_qubits=None, circuit=None, initial_state=0, *args, **kwargs):
"""
Sample the circuit. If circuit natively equips paulistrings, sample therefrom.
Expand Down Expand Up @@ -585,6 +628,9 @@ def do_simulate(self, variables, initial_state, *args, **kwargs) -> QubitWaveFun
"""
raise TequilaException("Backend Handler needs to be overwritten for supported simulators")

def do_simulate_density(self, variables, initial_state, *args, **kwargs) -> DensityMatrix:
raise TequilaException("Backend Handler needs to be overwritten for supported simulators")

def convert_measurements(self, backend_result) -> QubitWaveFunction:
raise TequilaException("Backend Handler needs to be overwritten for supported simulators")

Expand Down Expand Up @@ -795,16 +841,18 @@ def __copy__(self):
def __deepcopy__(self, memodict={}):
return type(self)(self.abstract_expectationvalue, **self._input_args)

def __call__(self, variables, samples: int = None, initial_state: Union[int, QubitWaveFunction] = 0, *args, **kwargs):

def __call__(self, variables, samples: int = None, simulate_density: bool = False, initial_state: Union[int, QubitWaveFunction] = 0, *args, **kwargs):
variables = format_variable_dictionary(variables=variables)
if self._variables is not None and len(self._variables) > 0:
if variables is None or (not set(self._variables) <= set(variables.keys())):
raise TequilaException(
"BackendExpectationValue received not all variables. Circuit depends on variables {}, you gave {}".format(
self._variables, variables))

if samples is None:

if simulate_density:
data = self.simulate_density(variables=variables, *args, **kwargs)

elif samples is None:
data = self.simulate(variables=variables, initial_state=initial_state, *args, **kwargs)
else:
data = self.sample(variables=variables, samples=samples, initial_state=initial_state, *args, **kwargs)
Expand Down Expand Up @@ -933,3 +981,17 @@ def simulate(self, variables, initial_state: Union[int, QubitWaveFunction], *arg
final_E += wfn.compute_expectationvalue(operator=H)
result.append(to_float(final_E))
return numpy.asarray(result)

def simulate_density(self, variables, noise_model = None, *args, **kwargs):
"""
Simulate the expectationvalue, using densities with noise

"""
self.update_variables(variables)
result = []
for H in self.H:
final_E = 0.0
density = self.U.simulate_density(variables=variables, noise_model=noise_model, *args, **kwargs)
final_E += density.QubitHamiltonian_expectation(hamiltonian=H)
result.append(to_float(final_E))
return numpy.asarray(result)
Loading
Loading