def test_modes_subset(depth): """Tests that the compiler recognizes which modes are not being modified and acts accordingly""" width = 10 eng = sf.LocalEngine(backend="gaussian") eng1 = sf.LocalEngine(backend="gaussian") circuit = sf.Program(width) indices = (1, 4, 2, 6, 7) active_modes = len(indices) with circuit.context as q: for _ in range(depth): U, s, V, _ = random_params(active_modes, 2.0 / depth, 1.0) ops.Interferometer(U) | tuple(q[i] for i in indices) for i, index in enumerate(indices): ops.Sgate(s[i]) | q[index] ops.Interferometer(V) | tuple(q[i] for i in indices) compiled_circuit = circuit.compile(compiler="gaussian_unitary") cv = eng.run(circuit).state.cov() mean = eng.run(circuit).state.means() cv1 = eng1.run(compiled_circuit).state.cov() mean1 = eng1.run(compiled_circuit).state.means() assert np.allclose(cv, cv1) assert np.allclose(mean, mean1) assert len(compiled_circuit.circuit[0].reg) == 5 indices = [compiled_circuit.circuit[0].reg[i].ind for i in range(5)] assert indices == sorted(list(indices))
def test_non_primitive_gates(): """Tests that the compiler is able to compile a number of non-primitive Gaussian gates""" width = 6 eng = sf.LocalEngine(backend="gaussian") eng1 = sf.LocalEngine(backend="gaussian") circuit = sf.Program(width) A = np.random.rand(width, width) + 1j * np.random.rand(width, width) A = A + A.T valsA = np.linalg.svd(A, compute_uv=False) A = A / 2 * np.max(valsA) B = np.random.rand( width // 2, width // 2) + 1j * np.random.rand(width // 2, width // 2) valsB = np.linalg.svd(B, compute_uv=False) B = B / 2 * valsB B = np.block([[0 * B, B], [B.T, 0 * B]]) with circuit.context as q: ops.GraphEmbed(A) | q ops.BipartiteGraphEmbed(B) | q ops.Pgate(0.1) | q[1] ops.CXgate(0.2) | (q[0], q[1]) ops.MZgate(0.4, 0.5) | (q[2], q[3]) ops.Fourier | q[0] ops.Xgate(0.4) | q[1] ops.Zgate(0.5) | q[3] compiled_circuit = circuit.compile(compiler="gaussian_unitary") cv = eng.run(circuit).state.cov() mean = eng.run(circuit).state.means() cv1 = eng1.run(compiled_circuit).state.cov() mean1 = eng1.run(compiled_circuit).state.means() assert np.allclose(cv, cv1) assert np.allclose(mean, mean1)
def test_symplectic_composition(depth, width): """Tests that symplectic operations are composed correctly""" eng = sf.LocalEngine(backend="gaussian") eng1 = sf.LocalEngine(backend="gaussian") circuit = sf.Program(width) Snet = np.identity(2 * width) with circuit.context as q: for _ in range(depth): S = random_symplectic(width, scale=0.2) Snet = S @ Snet ops.GaussianTransform(S) | q compiled_circuit = circuit.compile(compiler="gaussian_unitary") assert np.allclose(compiled_circuit.circuit[0].op.p[0], Snet)
def _sample_sf(p: sf.Program, shots: int = 1, backend_options: Optional[dict] = None) -> np.ndarray: """Generate samples from Strawberry Fields. Args: p (sf.Program): the program to sample from shots (int): the number of samples; defaults to 1 backend_options (dict[str, Any]): dictionary specifying options used by backends during sampling; defaults to :const:`BACKEND_DEFAULTS` Returns: array: an array of ``len(shots)`` samples, with each sample the result of running a :class:`~strawberryfields.program.Program` specified by ``p`` """ backend_options = {**BACKEND_DEFAULTS, **(backend_options or {})} if not backend_options["backend"] in QUANTUM_BACKENDS: raise ValueError("Invalid backend selected") if backend_options["remote"]: raise ValueError("Remote sampling not yet available") eng = sf.LocalEngine(backend=backend_options["backend"]) return np.array(eng.run(p, run_options={"shots": shots}).samples)
def sample( A: np.ndarray, n_mean: float, n_samples: int = 1, threshold: bool = True, loss: float = 0.0 ) -> list: r"""Generate simulated samples from GBS encoded with a symmetric matrix :math:`A`. **Example usage:** >>> g = nx.erdos_renyi_graph(5, 0.7) >>> a = nx.to_numpy_array(g) >>> sample(a, 3, 4) [[1, 1, 1, 1, 1], [1, 1, 0, 1, 1], [0, 0, 0, 0, 0], [1, 0, 0, 0, 1]] Args: A (array): the symmetric matrix to sample from n_mean (float): mean photon number n_samples (int): number of samples threshold (bool): perform GBS with threshold detectors if ``True`` or photon-number resolving detectors if ``False`` loss (float): fraction of generated photons that are lost while passing through device. Parameter should range from ``loss=0`` (ideal noise-free GBS) to ``loss=1``. Returns: list[list[int]]: a list of samples from GBS with respect to the input symmetric matrix """ if not np.allclose(A, A.T): raise ValueError("Input must be a NumPy array corresponding to a symmetric matrix") if n_samples < 1: raise ValueError("Number of samples must be at least one") if n_mean < 0: raise ValueError("Mean photon number must be non-negative") if not 0 <= loss <= 1: raise ValueError("Loss parameter must take a value between zero and one") nodes = len(A) p = sf.Program(nodes) eng = sf.LocalEngine(backend="gaussian") mean_photon_per_mode = n_mean / float(nodes) # pylint: disable=expression-not-assigned,pointless-statement with p.context as q: sf.ops.GraphEmbed(A, mean_photon_per_mode=mean_photon_per_mode) | q if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q if threshold: sf.ops.MeasureThreshold() | q else: sf.ops.MeasureFock() | q s = eng.run(p, run_options={"shots": n_samples}).samples if n_samples == 1: return [s] return s.tolist()
def test_tensorflow_not_installed(self, monkeypatch): """Test that an exception is raised if TensorFlow is not installed""" with monkeypatch.context() as m: # force Python check to pass m.setattr("sys.version_info", (3, 6, 3)) with pytest.raises(ImportError, message="version 1.3 of TensorFlow is required"): reload(sf.backends.tfbackend) sf.LocalEngine('tf')
def prob_event_mc( graph: nx.Graph, photon_number: int, max_count_per_mode: int, n_mean: float = 5, samples: int = 1000, ) -> float: """Gives a Monte Carlo estimate of the probability of a given event for a GBS device encoded according to the input graph. To make this estimate, several samples from the event are drawn uniformly at random. For each sample, we calculate the probability of observing that sample from a GBS programmed according to the input graph and mean photon number. These probabilities are then rescaled according to the cardinality of the event. The estimate is the sample mean of the rescaled probabilities. To make this estimate, several samples from the event are drawn uniformly at random using :func:event_to_sample. **Example usage**: >>> graph = nx.complete_graph(8) >>> p_event_mc(graph, 4, 2) 0.1395 Args: graph (nx.Graph): input graph encoded in the GBS device photon_number (int): number of photons in the event max_count_per_mode (int): maximum number of photons per mode in the event n_mean (float): total mean photon number of the GBS device samples (int): number of samples used in the Monte Carlo estimation Returns: float: the estimated probability """ modes = graph.order() A = nx.to_numpy_array(graph) mean_photon_per_mode = n_mean / float(modes) p = sf.Program(modes) # pylint: disable=expression-not-assigned with p.context as q: sf.ops.GraphEmbed(A, mean_photon_per_mode=mean_photon_per_mode) | q eng = sf.LocalEngine(backend="gaussian") result = eng.run(p) prob = 0 for _ in range(samples): sample = event_to_sample(photon_number, max_count_per_mode, modes) prob += result.state.fock_prob(sample, cutoff=photon_number + 1) prob = prob * event_cardinality(photon_number, max_count_per_mode, modes) / samples return prob
def test_tensorflow_not_installed(self, monkeypatch): """Test that an exception is raised if TensorFlow is not installed""" with monkeypatch.context() as m: # Force the Python check to pass. m.setattr(sys, "version_info", (3, 6, 3)) with pytest.raises(ImportError, match="version 2.x of TensorFlow is required"): sf.LocalEngine("tf")
def prob_orbit_mc(graph: nx.Graph, orbit: list, n_mean: float = 5, samples: int = 1000) -> float: """Gives a Monte Carlo estimate of the probability of a given orbit for a GBS device encoded according to the input graph. To make this estimate, several samples from the orbit are drawn uniformly at random using :func:`orbit_to_sample`. For each sample, this function calculates the probability of observing that sample from a GBS device programmed according to the input graph and mean photon number. The sum of the probabilities is then rescaled according to the cardinality of the orbit and the total number of samples. The estimate is the sample mean of the rescaled probabilities. To make this estimate, several samples from the orbit are drawn uniformly at random using :func:orbit_to_sample. **Example usage**: >>> graph = nx.complete_graph(8) >>> prob_orbit_mc(graph, [2, 1, 1]) 0.03744 Args: graph (nx.Graph): input graph encoded in the GBS device orbit (list[int]): orbit for which to estimate the probability n_mean (float): total mean photon number of the GBS device samples (int): number of samples used in the Monte Carlo estimation Returns: float: estimated orbit probability """ modes = graph.order() photons = sum(orbit) A = nx.to_numpy_array(graph) mean_photon_per_mode = n_mean / float(modes) p = sf.Program(modes) # pylint: disable=expression-not-assigned with p.context as q: sf.ops.GraphEmbed(A, mean_photon_per_mode=mean_photon_per_mode) | q eng = sf.LocalEngine(backend="gaussian") result = eng.run(p) prob = 0 for _ in range(samples): sample = orbit_to_sample(orbit, modes) prob += result.state.fock_prob(sample, cutoff=photons + 1) prob = prob * orbit_cardinality(orbit, modes) / samples return prob
def test_displacements_only(depth, width): """Tests that a circuit and its compiled version produce the same Gaussian state when there are only displacements""" eng = sf.LocalEngine(backend="gaussian") eng1 = sf.LocalEngine(backend="gaussian") circuit = sf.Program(width) with circuit.context as q: for _ in range(depth): alphas = np.random.rand(width) + 1j * np.random.rand(width) for i in range(width): ops.Dgate(np.abs(alphas[i]), np.angle(alphas[i])) | q[i] compiled_circuit = circuit.compile(compiler="gaussian_unitary") cv = eng.run(circuit).state.cov() mean = eng.run(circuit).state.means() cv1 = eng1.run(compiled_circuit).state.cov() mean1 = eng1.run(compiled_circuit).state.means() assert np.allclose(cv, cv1) assert np.allclose(mean, mean1)
def test_incorrect_python_version(self, monkeypatch): """Test that an exception is raised if the version of Python installed is > 3.6""" with monkeypatch.context() as m: m.setattr("sys.version_info", (3, 8, 1)) m.setattr(tensorflow, "__version__", "1.12.2") with pytest.raises(ImportError, message="you will need to install Python 3.6"): reload(sf.backends.tfbackend) sf.LocalEngine('tf')
def test_modes_subset(depth): """Tests that the compiler recognizes which modes are not being modified and acts accordingly""" width = 10 eng = sf.LocalEngine(backend="gaussian") eng1 = sf.LocalEngine(backend="gaussian") circuit = sf.Program(width) indices = (1, 4, 2, 6, 7) active_modes = len(indices) with circuit.context as q: for _ in range(depth): U = unitary_group.rvs(len(indices)) ops.Interferometer(U) | tuple(q[i] for i in indices) compiled_circuit = circuit.compile(compiler="passive") assert len(compiled_circuit.circuit[0].reg) == 5 indices = [compiled_circuit.circuit[0].reg[i].ind for i in range(5)] assert indices == sorted(list(indices))
def test_all_passive_gates(hbar, tol): """test that all gates run and do not cause anything to crash""" eng = sf.LocalEngine(backend="gaussian") circuit = sf.Program(4) with circuit.context as q: for i in range(4): ops.Sgate(1, 0.3) | q[i] ops.Rgate(np.pi) | q[0] ops.PassiveChannel(np.ones((2, 2))) | (q[1], q[2]) ops.LossChannel(0.9) | q[1] ops.MZgate(0.25 * np.pi, 0) | (q[2], q[3]) ops.PassiveChannel(np.array([[0.83]])) | q[0] ops.sMZgate(0.11, -2.1) | (q[0], q[3]) ops.Interferometer(np.array([[np.exp(1j * 2)]])) | q[1] ops.BSgate(0.8, 0.4) | (q[1], q[3]) ops.Interferometer(0.5**0.5 * np.fft.fft(np.eye(2))) | (q[0], q[2]) ops.PassiveChannel(0.1 * np.ones((3, 3))) | (q[3], q[1], q[0]) cov = eng.run(circuit).state.cov() circuit = sf.Program(4) with circuit.context as q: ops.Rgate(np.pi) | q[0] ops.PassiveChannel(np.ones((2, 2))) | (q[1], q[2]) ops.LossChannel(0.9) | q[1] ops.MZgate(0.25 * np.pi, 0) | (q[2], q[3]) ops.PassiveChannel(np.array([[0.83]])) | q[0] ops.sMZgate(0.11, -2.1) | (q[0], q[3]) ops.Interferometer(np.array([[np.exp(1j * 2)]])) | q[1] ops.BSgate(0.8, 0.4) | (q[1], q[3]) ops.Interferometer(0.5**0.5 * np.fft.fft(np.eye(2))) | (q[0], q[2]) ops.PassiveChannel(0.1 * np.ones((3, 3))) | (q[3], q[1], q[0]) compiled_circuit = circuit.compile(compiler="passive") T = compiled_circuit.circuit[0].op.p[0] S_sq = np.eye(8, dtype=np.complex128) r = 1 phi = 0.3 for i in range(4): S_sq[i, i] = np.cosh(r) - np.sinh(r) * np.cos(phi) S_sq[i, i + 4] = -np.sinh(r) * np.sin(phi) S_sq[i + 4, i] = -np.sinh(r) * np.sin(phi) S_sq[i + 4, i + 4] = np.cosh(r) + np.sinh(r) * np.cos(phi) cov_sq = (hbar / 2) * S_sq @ S_sq.T mu = np.zeros(8) P = interferometer(T) L = (hbar / 2) * (np.eye(P.shape[0]) - P @ P.T) cov2 = P @ cov_sq @ P.T + L assert np.allclose(cov, cov2, atol=tol, rtol=0)
def test_incorrect_tf_version(self, monkeypatch): """Test that an exception is raised if the version of TensorFlow installed is not version 2.x""" with monkeypatch.context() as m: # force Python check to pass m.setattr("sys.version_info", (3, 6, 3)) m.setattr(tensorflow, "__version__", "1.12.2") with pytest.raises(ImportError, match="version 2.x of TensorFlow is required"): reload(sf.backends.tfbackend) sf.LocalEngine('tf')
def test_gaussian_program(depth, width): """Tests that a circuit and its compiled version produce the same Gaussian state""" eng = sf.LocalEngine(backend="gaussian") eng1 = sf.LocalEngine(backend="gaussian") circuit = sf.Program(width) with circuit.context as q: for _ in range(depth): U, s, V, alphas = random_params(width, 2.0 / depth, 1.0) ops.Interferometer(U) | q for i in range(width): ops.Sgate(s[i]) | q[i] ops.Interferometer(V) | q for i in range(width): ops.Dgate(np.abs(alphas[i]), np.angle(alphas[i])) | q[i] compiled_circuit = circuit.compile(compiler="gaussian_unitary") cv = eng.run(circuit).state.cov() mean = eng.run(circuit).state.means() cv1 = eng1.run(compiled_circuit).state.cov() mean1 = eng1.run(compiled_circuit).state.means() assert np.allclose(cv, cv1) assert np.allclose(mean, mean1)
def test_op_prob(self, params): """Test if the function ``strawberryfields.apps.qchem.vibronic.VibronicTransition`` gives the correct probabilities of all possible Fock basis states when used in a circuit""" U1, r, U2, alpha, prob = params eng = sf.LocalEngine(backend="gaussian") gbs = sf.Program(len(U1)) with gbs.context as q: vibronic.VibronicTransition(U1, r, U2, alpha) | q p = eng.run(gbs).state.all_fock_probs(cutoff=4) assert np.allclose(p, prob)
def test_all_loss(self, monkeypatch, adj, dim): """Test if function samples from the vacuum when maximum loss is applied.""" mock_eng_run = mock.MagicMock() with monkeypatch.context() as m: m.setattr(sf.LocalEngine, "run", mock_eng_run) sample.sample(A=adj, n_mean=1, threshold=False, loss=1) p_func = mock_eng_run.call_args[0][0] eng = sf.LocalEngine(backend="gaussian") state = eng.run(p_func).state cov = state.cov() disp = state.displacement() assert np.allclose(cov, 0.5 * state.hbar * np.eye(2 * dim)) assert np.allclose(disp, np.zeros(dim))
def test_incorrect_tf_version(self, monkeypatch): """Test that an exception is raised if the version of TensorFlow installed is not version 2.x""" with monkeypatch.context() as m: # Force the Python check to pass. m.setattr(sys, "version_info", (3, 6, 3)) # Unload the TF backend to ensure sf.LocalEngine() will run __init__.py. m.delitem(sys.modules, "strawberryfields.backends.tfbackend", raising=False) # Set the TF version in case the existing version is valid. m.setitem(sys.modules, "tensorflow", MagicMock(__version__="1.2.3")) with pytest.raises(ImportError, match="version 2.x of TensorFlow is required"): sf.LocalEngine("tf")
def test_zgate_decompose(self, backend, hbar, applied_cmds): """Test parameter processing occuring within the Zgate._decompose method.""" import tensorflow as tf mapping = {"p": tf.Variable(0.1)} prog = self.create_program(sf.ops.Zgate, mapping) # verify bound parameters are correct assert prog.free_params["p"].val is mapping["p"] # assert executed program is constructed correctly eng = sf.LocalEngine(backend) result = eng.run(prog, args=mapping) assert len(applied_cmds) == 1 assert isinstance(applied_cmds[0].op, sf.ops.Dgate) assert par_evaluate(applied_cmds[0].op.p[0]) == mapping["p"] / np.sqrt(2 * hbar) assert applied_cmds[0].op.p[1] == np.pi / 2
def test_two_mode_gate_complex_phase(self, backend, gate, applied_cmds): """Test non-decomposed two-mode gates with complex phase arguments.""" import tensorflow as tf mapping = {'r': tf.Variable(0.1), 'phi': tf.Variable(0.2)} prog = self.create_program(gate, mapping) # verify bound parameters are correct assert prog.free_params['r'].val is mapping['r'] assert prog.free_params['phi'].val is mapping['phi'] # assert executed program is constructed correctly eng = sf.LocalEngine(backend) result = eng.run(prog, args=mapping) assert len(applied_cmds) == 1 assert isinstance(applied_cmds[0].op, gate) assert applied_cmds[0].op.p[0].val == mapping["r"] assert applied_cmds[0].op.p[1].val == mapping["phi"]
def test_all_loss(self, monkeypatch): """Test if function samples from the vacuum when maximum loss is applied.""" dim = 5 graph = nx.complete_graph(dim) mock_eng_run = mock.MagicMock() with monkeypatch.context() as m: m.setattr(sf.LocalEngine, "run", mock_eng_run) similarity.prob_event_mc(graph, 6, 3, samples=1, loss=1) p_func = mock_eng_run.call_args[0][0] eng = sf.LocalEngine(backend="gaussian") state = eng.run(p_func).state cov = state.cov() disp = state.displacement() assert np.allclose(cov, 0.5 * state.hbar * np.eye(2 * dim)) assert np.allclose(disp, np.zeros(dim))
def test_all_loss(self, monkeypatch, p): """Test if function samples from the vacuum when maximum loss is applied. This test is only done for the zero temperature case""" if p == 'p0': dim = len(alpha) mock_eng_run = mock.MagicMock() with monkeypatch.context() as m: m.setattr(sf.LocalEngine, "run", mock_eng_run) sample.vibronic(*p, 1, loss=1) p_func = mock_eng_run.call_args[0][0] eng = sf.LocalEngine(backend="gaussian") state = eng.run(p_func).state cov = state.cov() disp = state.displacement() assert np.allclose(cov, 0.5 * state.hbar * np.eye(2 * dim)) assert np.allclose(disp, np.zeros(dim))
def _get_state(graph: nx.Graph, n_mean: float = 5, loss: float = 0.0) -> BaseGaussianState: r"""Embeds the input graph into a GBS device and returns the corresponding Gaussian state.""" modes = graph.order() A = nx.to_numpy_array(graph) mean_photon_per_mode = n_mean / float(modes) p = sf.Program(modes) # pylint: disable=expression-not-assigned with p.context as q: sf.ops.GraphEmbed(A, mean_photon_per_mode=mean_photon_per_mode) | q if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q eng = sf.LocalEngine(backend="gaussian") return eng.run(p).state
def sample_tmsv( r: list, t: float, Ul: np.ndarray, w: np.ndarray, n_samples: int, loss: float = 0.0, ) -> list: r"""Generate samples for simulating vibrational quantum dynamics with a two-mode squeezed vacuum input state. This function generates samples from a GBS device with two-mode squeezed vacuum input states. Given :math:`N` squeezing parameters and an :math:`N`-dimensional normal-to-local transformation matrix, a GBS device with :math:`2N` modes is simulated. The :func:`~.TimeEvolution` operator acts only on the first :math:`N` modes in the device. Samples are generated by measuring the number of photons in each of the :math:`2N` modes. **Example usage:** >>> r = [[0.2, 0.1], [0.8, 0.2]] >>> t = 10.0 >>> Ul = np.array([[0.707106781, -0.707106781], ... [0.707106781, 0.707106781]]) >>> w = np.array([3914.92, 3787.59]) >>> n_samples = 5 >>> sample_tmsv(r, t, Ul, w, n_samples) [[0, 0, 0, 0], [0, 0, 0, 0], [0, 1, 0, 1], [0, 1, 0, 1], [0, 2, 0, 2]] Args: r (list[list[float]]): list of two-mode squeezing gate parameters given as ``[amplitude, phase]`` for all modes t (float): time in femtoseconds Ul (array): normal-to-local transformation matrix w (array): normal mode frequencies :math:`\omega` in units of :math:`\mbox{cm}^{-1}` n_samples (int): number of samples to be generated loss (float): loss parameter denoting the fraction of lost photons Returns: list[list[int]]: a list of samples """ if np.any(np.iscomplex(Ul)): raise ValueError( "The normal mode to local mode transformation matrix must be real") if n_samples < 1: raise ValueError("Number of samples must be at least one") if not len(r) == len(Ul): raise ValueError( "Number of squeezing parameters and the number of modes in the normal-to-local" " transformation matrix must be equal") N = len(Ul) eng = sf.LocalEngine(backend="gaussian") prog = sf.Program(2 * N) # pylint: disable=expression-not-assigned with prog.context as q: for i in range(N): sf.ops.S2gate(r[i][0], r[i][1]) | (q[i], q[i + N]) sf.ops.Interferometer(Ul.T) | q[:N] TimeEvolution(w, t) | q[:N] sf.ops.Interferometer(Ul) | q[:N] if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") s = eng.run(prog, shots=n_samples).samples return s.tolist()
def prob_event_mc( graph: nx.Graph, photon_number: int, max_count_per_mode: int, n_mean: float = 5, samples: int = 1000, loss: float = 0.0, ) -> float: r"""Gives a Monte Carlo estimate of the GBS probability of a given event according to the input graph. To make this estimate, several samples from the event are drawn uniformly at random using :func:`event_to_sample`. The GBS probabilities of these samples are then calculated and the sum is used to create an estimate of the event probability. **Example usage:** >>> graph = nx.complete_graph(8) >>> prob_event_mc(graph, 4, 2) 0.1395 Args: graph (nx.Graph): input graph encoded in the GBS device photon_number (int): number of photons in the event max_count_per_mode (int): maximum number of photons per mode in the event n_mean (float): total mean photon number of the GBS device samples (int): number of samples used in the Monte Carlo estimation loss (float): fraction of photons lost in GBS Returns: float: estimated orbit probability """ if samples < 1: raise ValueError("Number of samples must be at least one") if n_mean < 0: raise ValueError("Mean photon number must be non-negative") if not 0 <= loss <= 1: raise ValueError( "Loss parameter must take a value between zero and one") if photon_number < 0: raise ValueError("Photon number must not be below zero") if max_count_per_mode < 0: raise ValueError( "Maximum number of photons per mode must be non-negative") modes = graph.order() A = nx.to_numpy_array(graph) mean_photon_per_mode = n_mean / float(modes) p = sf.Program(modes) # pylint: disable=expression-not-assigned with p.context as q: sf.ops.GraphEmbed(A, mean_photon_per_mode=mean_photon_per_mode) | q if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q eng = sf.LocalEngine(backend="gaussian") result = eng.run(p) prob = 0 for _ in range(samples): sample = event_to_sample(photon_number, max_count_per_mode, modes) prob += result.state.fock_prob(sample, cutoff=photon_number + 1) prob = prob * event_cardinality(photon_number, max_count_per_mode, modes) / samples return prob
def vibronic( t: np.ndarray, U1: np.ndarray, r: np.ndarray, U2: np.ndarray, alpha: np.ndarray, n_samples: int, loss: float = 0.0, engine='local', ) -> list: """Generate samples for computing vibronic spectra. The following gates are applied to input vacuum states: 1. Two-mode squeezing on all :math:`2N` modes with parameters ``t`` 2. Interferometer ``U1`` on the first :math:`N` modes 3. Squeezing on the first :math:`N` modes with parameters ``r`` 4. Interferometer ``U2`` on the first :math:`N` modes 5. Displacement on the first :math:`N` modes with parameters ``alpha`` A sample is generated by measuring the number of photons in each of the :math:`2N` modes. In the special case that all of the two-mode squeezing parameters ``t`` are zero, only :math:`N` modes are considered, which speeds up calculations. **Example usage:** >>> formic = data.Formic() >>> w = formic.w >>> wp = formic.wp >>> Ud = formic.Ud >>> delta = formic.delta >>> T = 0 >>> t, U1, r, U2, alpha = vibronic.gbs_params(w, wp, Ud, delta, T) >>> sample.vibronic(t, U1, r, U2, alpha, 2, 0.0) [[0, 0, 2, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]] Args: t (array): two-mode squeezing parameters U1 (array): unitary matrix for the first interferometer r (array): squeezing parameters U2 (array): unitary matrix for the second interferometer alpha (array): displacement parameters n_samples (int): number of samples to be generated loss (float): loss parameter denoting the fraction of generated photons that are lost Returns: list[list[int]]: a list of samples from GBS """ if n_samples < 1: raise ValueError("Number of samples must be at least one") if not 0 <= loss <= 1: raise ValueError("Loss parameter must take a value between zero and one") n_modes = len(t) if engine=='X8': #VGG check you engine using sf.ping() print("Atemting to use X8 ... ") eng = sf.RemoteEngine("X8") else: eng = sf.LocalEngine(backend="gaussian") if np.any(t != 0): gbs = sf.Program(n_modes * 2) else: gbs = sf.Program(n_modes) # pylint: disable=expression-not-assigned,pointless-statement with gbs.context as q: if np.any(t != 0): for i in range(n_modes): sf.ops.S2gate(t[i]) | (q[i], q[i + n_modes]) sf.ops.Interferometer(U1) | q[:n_modes] for i in range(n_modes): sf.ops.Sgate(r[i]) | q[i] sf.ops.Interferometer(U2) | q[:n_modes] for i in range(n_modes): sf.ops.Dgate(alpha[i]) | q[i] if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") if engine=='X8': #VGG check you engine using sf.ping() s = eng.run(gbs,shots=n_samples,state=True).samples else: s = eng.run(gbs, shots=n_samples).samples s = np.array(s).tolist() # convert all generated samples to list if np.any(t == 0): s = np.pad(s, ((0, 0), (0, n_modes))).tolist() return s
def test_load_backend(self): """Backend can be correctly loaded via strings""" eng = sf.LocalEngine("base") assert isinstance(eng.backend, BaseBackend)
def test_bad_backend(self): """Backend must be a string or a BaseBackend instance.""" with pytest.raises(TypeError, match='backend must be a string or a BaseBackend instance'): eng = sf.LocalEngine(0)
def eng(backend): """Engine fixture.""" return sf.LocalEngine(backend)
def sample_coherent( alpha: list, t: float, Ul: np.ndarray, w: np.ndarray, n_samples: int, loss: float = 0.0, ) -> list: r"""Generate samples for simulating vibrational quantum dynamics with an input coherent state. **Example usage:** >>> alpha = [[0.3, 0.5], [1.4, 0.1]] >>> t = 10.0 >>> Ul = np.array([[0.707106781, -0.707106781], ... [0.707106781, 0.707106781]]) >>> w = np.array([3914.92, 3787.59]) >>> n_samples = 5 >>> sample_coherent(alpha, t, Ul, w, n_samples) [[0, 2], [0, 1], [0, 3], [0, 2], [0, 1]] Args: alpha (list[list[float]]): list of displacement parameters given as ``[magnitudes, angles]`` for all modes t (float): time in femtoseconds Ul (array): normal-to-local transformation matrix w (array): normal mode frequencies :math:`\omega` in units of :math:`\mbox{cm}^{-1}` n_samples (int): number of samples to be generated loss (float): loss parameter denoting the fraction of lost photons Returns: list[list[int]]: a list of samples """ if np.any(np.iscomplex(Ul)): raise ValueError( "The normal mode to local mode transformation matrix must be real") if n_samples < 1: raise ValueError("Number of samples must be at least one") if not len(alpha) == len(Ul): raise ValueError( "Number of displacement parameters and the number of modes in the normal-to-local" " transformation matrix must be equal") modes = len(Ul) eng = sf.LocalEngine(backend="gaussian") prog = sf.Program(modes) # pylint: disable=expression-not-assigned with prog.context as q: for i in range(modes): sf.ops.Dgate(alpha[i][0], alpha[i][1]) | q[i] sf.ops.Interferometer(Ul.T) | q TimeEvolution(w, t) | q sf.ops.Interferometer(Ul) | q if loss: for _q in q: sf.ops.LossChannel(1 - loss) | _q sf.ops.MeasureFock() | q with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="Cannot simulate non-") s = eng.run(prog, shots=n_samples).samples return s.tolist()