"""MBQC simulator
Simulates MBQC by executing the pattern.
"""
import warnings
import numpy as np
from graphix.noise_models import NoiseModel
from graphix.sim.density_matrix import DensityMatrixBackend
from graphix.sim.statevec import StatevectorBackend
from graphix.sim.tensornet import TensorNetworkBackend
[docs]class PatternSimulator:
"""MBQC simulator
Executes the measurement pattern.
"""
[docs] def __init__(self, pattern, backend="statevector", noise_model=None, **kwargs):
"""
Parameters
-----------
pattern: :class:`graphix.pattern.Pattern` object
MBQC pattern to be simulated.
backend: str, 'statevector', 'densitymatrix or 'tensornetwork'
simulation backend (optional), default is 'statevector'.
noise_model:
kwargs: keyword args for specified backend.
.. seealso:: :class:`graphix.sim.statevec.StatevectorBackend`\
:class:`graphix.sim.tensornet.TensorNetworkBackend`\
:class:`graphix.sim.density_matrix.DensityMatrixBackend`\
"""
# check that pattern has output nodes configured
# assert len(pattern.output_nodes) > 0
if backend == "statevector" and noise_model is None:
self.noise_model = None
self.backend = StatevectorBackend(pattern, **kwargs)
elif backend == "densitymatrix":
if noise_model is None:
self.noise_model = None
# no noise: no need to compute probabilities
self.backend = DensityMatrixBackend(pattern, **kwargs)
warnings.warn(
"Simulating using densitymatrix backend with no noise. To add noise to the simulation, give an object of `graphix.noise_models.Noisemodel` to `noise_model` keyword argument."
)
if noise_model is not None:
self.set_noise_model(noise_model)
# if noise: have to compute the probabilities
self.backend = DensityMatrixBackend(pattern, pr_calc=True, **kwargs)
elif backend in {"tensornetwork", "mps"} and noise_model is None:
self.noise_model = None
self.backend = TensorNetworkBackend(pattern, **kwargs)
# TODO or just do the noiseless sim with a warning?
elif backend in {"statevector", "tensornetwork", "mps"} and noise_model is not None:
raise ValueError(f"The backend {backend} doesn't support noise but noisemodel was provided.")
else:
raise ValueError("Unknown backend.")
self.pattern = pattern
self.node_index = []
def set_noise_model(self, model):
self.noise_model = model
@property
def results(self):
return self.backend.results
@property
def state(self):
return self.backend.state
[docs] def run(self):
"""Perform the simulation.
Returns
-------
state :
the output quantum state,
in the representation depending on the backend used.
"""
self.backend.add_nodes(self.pattern.input_nodes)
if self.noise_model is None:
for cmd in self.pattern:
if cmd[0] == "N":
self.backend.add_nodes([cmd[1]])
elif cmd[0] == "E":
self.backend.entangle_nodes(cmd[1])
elif cmd[0] == "M":
self.backend.measure(cmd)
elif cmd[0] == "X":
self.backend.correct_byproduct(cmd)
elif cmd[0] == "Z":
self.backend.correct_byproduct(cmd)
elif cmd[0] == "C":
self.backend.apply_clifford(cmd)
else:
raise ValueError("invalid commands")
self.backend.finalize()
else:
self.noise_model.assign_simulator(self)
for node in self.pattern.input_nodes:
self.backend.apply_channel(self.noise_model.prepare_qubit(), [node])
for cmd in self.pattern:
if cmd[0] == "N": # prepare clean qubit and apply channel
self.backend.add_nodes([cmd[1]])
self.backend.apply_channel(self.noise_model.prepare_qubit(), [cmd[1]])
elif cmd[0] == "E": # for "E" cmd[1] is already a tuyple
self.backend.entangle_nodes(cmd[1]) # for some reaon entangle doesn't get the whole command
self.backend.apply_channel(self.noise_model.entangle(), cmd[1])
elif cmd[0] == "M": # apply channel before measuring, then measur and confuse_result
self.backend.apply_channel(self.noise_model.measure(), [cmd[1]])
self.backend.measure(cmd)
self.noise_model.confuse_result(cmd)
elif cmd[0] == "X":
self.backend.correct_byproduct(cmd)
if np.mod(np.sum([self.results[j] for j in cmd[2]]), 2) == 1:
self.backend.apply_channel(self.noise_model.byproduct_x(), [cmd[1]])
elif cmd[0] == "Z":
self.backend.correct_byproduct(cmd)
if np.mod(np.sum([self.results[j] for j in cmd[2]]), 2) == 1:
self.backend.apply_channel(self.noise_model.byproduct_z(), [cmd[1]])
elif cmd[0] == "C":
self.backend.apply_clifford(cmd)
self.backend.apply_channel(self.noise_model.clifford(), [cmd[1]])
elif cmd[0] == "T":
# T command is a flag for one clock cycle in simulated experiment,
# to be added via hardware-agnostic pattern modifier
self.noise_model.tick_clock()
else:
raise ValueError("Invalid commands.")
self.backend.finalize()
return self.backend.state