diff --git a/benchmarks/tf_cvnn_benchmark.py b/benchmarks/tf_cvnn_benchmark.py index ee4f61eb..a84a8e44 100644 --- a/benchmarks/tf_cvnn_benchmark.py +++ b/benchmarks/tf_cvnn_benchmark.py @@ -106,6 +106,7 @@ def _pq_state_vector(weights, cutoff): return state.get_tensor_representation() +@tf.function(jit_compile=True) def _calculate_piquasso_results(weights, cutoff): with tf.GradientTape() as tape: state_vector = _pq_state_vector(weights, cutoff) diff --git a/piquasso/_backends/calculator.py b/piquasso/_backends/calculator.py index 673be83a..3999efe4 100644 --- a/piquasso/_backends/calculator.py +++ b/piquasso/_backends/calculator.py @@ -29,6 +29,14 @@ class _BuiltinCalculator(BaseCalculator): """Base class for built-in calculators.""" +def _noop_custom_gradient(func): + def noop_grad(*args, **kwargs): + result, _ = func(*args, **kwargs) + return result + + return noop_grad + + class NumpyCalculator(_BuiltinCalculator): """The calculations for a simulation using NumPy (and SciPy). @@ -38,6 +46,7 @@ class NumpyCalculator(_BuiltinCalculator): def __init__(self): self.np = np self.fallback_np = np + self.forward_pass_np = np self.block_diag = scipy.linalg.block_diag self.block = np.block self.logm = scipy.linalg.logm @@ -51,7 +60,9 @@ def __init__(self): self.hafnian = hafnian_with_reduction self.loop_hafnian = loop_hafnian_with_reduction - def maybe_convert_to_numpy(self, value): + self.custom_gradient = _noop_custom_gradient + + def preprocess_input_for_custom_gradient(self, value): return value def assign(self, array, index, value): @@ -112,7 +123,19 @@ class TensorflowCalculator(_BuiltinCalculator): gradient = tape.gradient(mean, [r]) """ - def __init__(self): + def __init__(self, no_custom_gradient=False): + """ + Args: + no_custom_gradient (bool, optional): Executes the forward pass + using Tensorflow, instead of NumPy. This is sometimes necessary, e.g., + when one wants to decorate Piquasso code with + `tf.function(jit_compile=True)`. In fact, when eager execution is + disabled in Tensorflow, this argument is automatically set to `True`. + Otherwise, it defaults to False. + + Raises: + ImportError: When TensorFlow is not available. + """ try: import tensorflow as tf except ImportError: @@ -131,9 +154,20 @@ def __init__(self): self._tf = tf self.np = tnp self.fallback_np = np + + no_custom_gradient = no_custom_gradient or not tf.executing_eagerly() + self.no_custom_gradient = no_custom_gradient + self.forward_pass_np = tnp if no_custom_gradient else np + self.custom_gradient = ( + _noop_custom_gradient if no_custom_gradient else tf.custom_gradient + ) + self.sqrtm = tf.linalg.sqrtm - def maybe_convert_to_numpy(self, value): + def preprocess_input_for_custom_gradient(self, value): + if self.no_custom_gradient: + return value + return value.numpy() if self._tf.is_tensor(value) else value def block_diag(self, *arrs): @@ -147,9 +181,34 @@ def permanent(self, matrix, rows, columns): return glynn_gray_permanent(matrix, rows, columns, np=self.np) def assign(self, array, index, value): - # NOTE: This is not as advanced as Numpy's indexing, only supports 1D arrays. + """ + NOTE: This method is very limited, and is a bit hacky, since TF does not support + item assignment through its NumPy API. + """ - return self._tf.tensor_scatter_nd_update(array, [[index]], [value]) + if isinstance(array, self.fallback_np.ndarray): + array[index] = value + + return array + + if isinstance(index, int): + return self._tf.tensor_scatter_nd_update(array, [[index]], [value]) + + # NOTE: When using `tf.function`, TensorFlow threw the following error: + # + # TypeError: Tensors in list passed to 'values' of 'ConcatV2' Op have types [int32, int64] that don't all match. # noqa: E501 + # + # To make it disappear, I had to convert all the indices to `int32`. + index = index.astype(self.fallback_np.int32) + + if not isinstance(index, np.ndarray) or not len(index.shape) == 2: + raise RuntimeError(f"Invalid index: {index}") + + array = self._tf.tensor_scatter_nd_update( + array, index.reshape(-1, 1), value.reshape(-1) + ) + + return array def block(self, arrays): # NOTE: This is not as advanced as `numpy.block`, this function only supports diff --git a/piquasso/_backends/fock/calculations.py b/piquasso/_backends/fock/calculations.py index aaa75b18..38b8d6a1 100644 --- a/piquasso/_backends/fock/calculations.py +++ b/piquasso/_backends/fock/calculations.py @@ -203,7 +203,7 @@ def calculate_interferometer_helper_indices(space): } -def calculate_interferometer_on_fock_space(interferometer, index_dict): +def calculate_interferometer_on_fock_space(interferometer, index_dict, calculator): """Calculates finite representation of interferometer in the Fock space. The function assumes the knowledge of the 1-particle unitary. @@ -219,6 +219,8 @@ def calculate_interferometer_on_fock_space(interferometer, index_dict): numpy.ndarray: Finite representation of interferometer in the Fock space """ + np = calculator.forward_pass_np + subspace_representations = [] subspace_representations.append(np.array([[1.0]], dtype=interferometer.dtype)) diff --git a/piquasso/_backends/fock/general/calculations.py b/piquasso/_backends/fock/general/calculations.py index e61b2555..9f382399 100644 --- a/piquasso/_backends/fock/general/calculations.py +++ b/piquasso/_backends/fock/general/calculations.py @@ -57,7 +57,7 @@ def _apply_passive_linear(state, interferometer, modes): subspace = state._get_subspace(dim=len(interferometer)) subspace_transformations = _get_interferometer_on_fock_space( - interferometer, subspace + interferometer, subspace, state._calculator ) _apply_passive_gate_matrix_to_state(state, subspace_transformations, modes) @@ -107,10 +107,12 @@ def _calculate_density_matrix_after_interferometer( return new_density_matrix -def _get_interferometer_on_fock_space(interferometer, space): +def _get_interferometer_on_fock_space(interferometer, space, calculator): index_dict = calculate_interferometer_helper_indices(space) - return calculate_interferometer_on_fock_space(interferometer, index_dict) + return calculate_interferometer_on_fock_space( + interferometer, index_dict, calculator + ) def particle_number_measurement( diff --git a/piquasso/_backends/fock/pure/calculations/__init__.py b/piquasso/_backends/fock/pure/calculations/__init__.py index 0301ed96..4174174a 100644 --- a/piquasso/_backends/fock/pure/calculations/__init__.py +++ b/piquasso/_backends/fock/pure/calculations/__init__.py @@ -118,14 +118,14 @@ def _apply_active_gate_matrix_to_state( @calculator.custom_gradient def _apply_active_gate_matrix(state_vector, matrix): - state_vector = calculator.maybe_convert_to_numpy(state_vector) - matrix = calculator.maybe_convert_to_numpy(matrix) + state_vector = calculator.preprocess_input_for_custom_gradient(state_vector) + matrix = calculator.preprocess_input_for_custom_gradient(matrix) state_index_matrix_list = calculate_state_index_matrix_list( space, auxiliary_subspace, mode ) new_state_vector = _calculate_state_vector_after_apply_active_gate( - state_vector, matrix, state_index_matrix_list + state_vector, matrix, state_index_matrix_list, calculator ) grad = _create_linear_active_gate_gradient_function( state_vector, matrix, state_index_matrix_list, calculator @@ -136,8 +136,13 @@ def _apply_active_gate_matrix(state_vector, matrix): def _calculate_state_vector_after_apply_active_gate( - state_vector, matrix, state_index_matrix_list + state_vector, + matrix, + state_index_matrix_list, + calculator, ): + np = calculator.forward_pass_np + new_state_vector = np.empty_like(state_vector, dtype=state_vector.dtype) is_batch = len(state_vector.shape) == 2 @@ -146,8 +151,12 @@ def _calculate_state_vector_after_apply_active_gate( for state_index_matrix in state_index_matrix_list: limit = state_index_matrix.shape[0] - new_state_vector[state_index_matrix] = np.einsum( - einsum_string, matrix[:limit, :limit], state_vector[state_index_matrix] + new_state_vector = calculator.assign( + new_state_vector, + state_index_matrix, + np.einsum( + einsum_string, matrix[:limit, :limit], state_vector[state_index_matrix] + ), ) return new_state_vector diff --git a/piquasso/_backends/fock/pure/calculations/passive_linear.py b/piquasso/_backends/fock/pure/calculations/passive_linear.py index 6de817a5..56a0e0b0 100644 --- a/piquasso/_backends/fock/pure/calculations/passive_linear.py +++ b/piquasso/_backends/fock/pure/calculations/passive_linear.py @@ -57,11 +57,11 @@ def _apply_passive_linear(state, interferometer, modes, calculator): def _get_interferometer_on_fock_space(interferometer, space, calculator): def _get_interferometer_with_gradient_callback(interferometer): - interferometer = calculator.maybe_convert_to_numpy(interferometer) + interferometer = calculator.preprocess_input_for_custom_gradient(interferometer) index_dict = calculate_interferometer_helper_indices(space) subspace_representations = calculate_interferometer_on_fock_space( - interferometer, index_dict + interferometer, index_dict, calculator ) grad = _calculate_interferometer_gradient_on_fock_space( interferometer, calculator, subspace_representations, index_dict @@ -201,10 +201,10 @@ def _apply_passive_gate_matrix_to_state( space = state._space def _apply_interferometer_matrix(state_vector, subspace_transformations): - state_vector = calculator.maybe_convert_to_numpy(state_vector) + state_vector = calculator.preprocess_input_for_custom_gradient(state_vector) subspace_transformations = [ - calculator.maybe_convert_to_numpy(matrix) + calculator.preprocess_input_for_custom_gradient(matrix) for matrix in subspace_transformations ] @@ -217,6 +217,7 @@ def _apply_interferometer_matrix(state_vector, subspace_transformations): state_vector, subspace_transformations, index_list, + calculator, ) grad = _create_linear_passive_gate_gradient_function( @@ -236,7 +237,10 @@ def _calculate_state_vector_after_interferometer( state_vector: np.ndarray, subspace_transformations: List[np.ndarray], index_list: List[np.ndarray], + calculator: BaseCalculator, ) -> np.ndarray: + np = calculator.forward_pass_np + new_state_vector = np.empty_like(state_vector) is_batch = len(state_vector.shape) == 2 @@ -244,8 +248,12 @@ def _calculate_state_vector_after_interferometer( einsum_string = "ij,jkl->ikl" if is_batch else "ij,jk->ik" for n, indices in enumerate(index_list): - new_state_vector[indices] = np.einsum( - einsum_string, subspace_transformations[n], state_vector[indices] + new_state_vector = calculator.assign( + new_state_vector, + indices, + np.einsum( + einsum_string, subspace_transformations[n], state_vector[indices] + ), ) return new_state_vector diff --git a/piquasso/_backends/fock/pure/state.py b/piquasso/_backends/fock/pure/state.py index c32dc621..c10d6d71 100644 --- a/piquasso/_backends/fock/pure/state.py +++ b/piquasso/_backends/fock/pure/state.py @@ -205,8 +205,8 @@ def mean_position(self, mode: int) -> np.ndarray: state_vector = self._state_vector accumulator = np.dot( - (multipliers * np.take(state_vector, left_indices)), - np.take(state_vector, right_indices), + (multipliers * np.take(state_vector, np.array(left_indices))), + np.take(state_vector, np.array(right_indices)), ) return np.real(accumulator) * fallback_np.sqrt(self._config.hbar / 2) diff --git a/piquasso/_math/fock.py b/piquasso/_math/fock.py index b37fbd8f..4b27dd79 100644 --- a/piquasso/_math/fock.py +++ b/piquasso/_math/fock.py @@ -168,11 +168,15 @@ def get_single_mode_squeezing_operator( ) -> np.ndarray: @self.calculator.custom_gradient def _single_mode_squeezing_operator(r, phi): - r = self.calculator.maybe_convert_to_numpy(r) - phi = self.calculator.maybe_convert_to_numpy(phi) + r = self.calculator.preprocess_input_for_custom_gradient(r) + phi = self.calculator.preprocess_input_for_custom_gradient(phi) matrix = create_single_mode_squeezing_matrix( - r, phi, self.cutoff, complex_dtype=self.config.complex_dtype + r, + phi, + self.cutoff, + complex_dtype=self.config.complex_dtype, + calculator=self._calculator, ) grad = create_single_mode_squeezing_gradient( r, @@ -300,11 +304,15 @@ def get_annihilation_operator(self, modes: Tuple[int, ...]) -> np.ndarray: def get_single_mode_displacement_operator(self, *, r, phi): @self.calculator.custom_gradient def _single_mode_displacement_operator(r, phi): - r = self.calculator.maybe_convert_to_numpy(r) - phi = self.calculator.maybe_convert_to_numpy(phi) + r = self.calculator.preprocess_input_for_custom_gradient(r) + phi = self.calculator.preprocess_input_for_custom_gradient(phi) matrix = create_single_mode_displacement_matrix( - r, phi, self.cutoff, complex_dtype=self.config.complex_dtype + r, + phi, + self.cutoff, + complex_dtype=self.config.complex_dtype, + calculator=self._calculator, ) grad = create_single_mode_displacement_gradient( r, diff --git a/piquasso/_math/gate_matrices.py b/piquasso/_math/gate_matrices.py index e703c51d..c0a958db 100644 --- a/piquasso/_math/gate_matrices.py +++ b/piquasso/_math/gate_matrices.py @@ -15,12 +15,15 @@ import numpy as np +from piquasso.api.calculator import BaseCalculator + def create_single_mode_displacement_matrix( r: float, phi: float, cutoff: int, complex_dtype: np.dtype, + calculator: BaseCalculator, ) -> np.ndarray: r""" This method generates the Displacement operator following a recursion rule. @@ -37,25 +40,32 @@ def create_single_mode_displacement_matrix( np.ndarray: The constructed Displacement matrix representing the Fock operator. """ + np = calculator.forward_pass_np + + transformation = [] + + for _ in range(cutoff): + transformation.append([0.0] * cutoff) + fock_indices = np.sqrt(np.arange(cutoff, dtype=complex_dtype)) displacement = r * np.exp(1j * phi) - transformation = np.zeros((cutoff,) * 2, dtype=complex_dtype) - transformation[0, 0] = np.exp(-0.5 * r**2) + transformation[0][0] = np.exp(-0.5 * r**2) + for row in range(1, cutoff): - transformation[row, 0] = ( - displacement / fock_indices[row] * transformation[row - 1, 0] + transformation[row][0] = ( + displacement / fock_indices[row] * transformation[row - 1][0] ) for row in range(cutoff): for col in range(1, cutoff): - transformation[row, col] = ( + transformation[row][col] = ( -np.conj(displacement) / fock_indices[col] - * transformation[row, col - 1] + * transformation[row][col - 1] ) + ( - fock_indices[row] / fock_indices[col] * transformation[row - 1, col - 1] + fock_indices[row] / fock_indices[col] * transformation[row - 1][col - 1] ) - return transformation + return np.array(transformation, dtype=complex_dtype) def create_single_mode_squeezing_matrix( @@ -63,6 +73,7 @@ def create_single_mode_squeezing_matrix( phi: float, cutoff: int, complex_dtype: np.dtype, + calculator: BaseCalculator, ) -> np.ndarray: """ This method generates the Squeezing operator following a recursion rule. @@ -81,35 +92,41 @@ def create_single_mode_squeezing_matrix( np.ndarray: The constructed Squeezing matrix representing the Fock operator. """ + np = calculator.forward_pass_np + sechr = 1.0 / np.cosh(r) A = np.exp(1j * phi) * np.tanh(r) - transformation = np.zeros((cutoff,) * 2, dtype=complex_dtype) - transformation[0, 0] = np.sqrt(sechr) + transformation = [] + + for _ in range(cutoff): + transformation.append([0.0] * cutoff) + + transformation[0][0] = np.sqrt(sechr) fock_indices = np.sqrt(np.arange(cutoff, dtype=complex_dtype)) for index in range(2, cutoff, 2): - transformation[index, 0] = ( + transformation[index][0] = ( -fock_indices[index - 1] / fock_indices[index] - * (transformation[index - 2, 0] * A) + * (transformation[index - 2][0] * A) ) for row in range(0, cutoff): for col in range(1, cutoff): if (row + col) % 2 == 0: - transformation[row, col] = ( + transformation[row][col] = ( 1 / fock_indices[col] * ( - (fock_indices[row] * transformation[row - 1, col - 1] * sechr) + (fock_indices[row] * transformation[row - 1][col - 1] * sechr) + ( fock_indices[col - 1] - * A.conj() - * transformation[row, col - 2] + * np.conj(A) + * transformation[row][col - 2] ) ) ) - return transformation + return np.array(transformation, dtype=complex_dtype) diff --git a/piquasso/api/calculator.py b/piquasso/api/calculator.py index da6982ef..5ec43b1c 100644 --- a/piquasso/api/calculator.py +++ b/piquasso/api/calculator.py @@ -30,6 +30,7 @@ class BaseCalculator(abc.ABC): np: Any fallback_np: Any + forward_pass_np: Any def __deepcopy__(self, memo: Any) -> "BaseCalculator": """ @@ -40,9 +41,9 @@ def __deepcopy__(self, memo: Any) -> "BaseCalculator": return self - def maybe_convert_to_numpy(self, value): + def preprocess_input_for_custom_gradient(self, value): """ - Converts tensorflow objects to numpy objects if applicable. + Applies modifications to inputs in custom gradients. """ raise NotImplementedCalculation() diff --git a/tests/backends/fock/test_gates.py b/tests/backends/fock/test_gates.py index bf4ba1b0..ee66216d 100644 --- a/tests/backends/fock/test_gates.py +++ b/tests/backends/fock/test_gates.py @@ -21,9 +21,15 @@ from functools import partial -TensorflowPureFockSimulator = partial( - pq.PureFockSimulator, - calculator=pq.TensorflowCalculator(), +tf_purefock_simulators = ( + partial( + pq.PureFockSimulator, + calculator=pq.TensorflowCalculator(), + ), + partial( + pq.PureFockSimulator, + calculator=pq.TensorflowCalculator(no_custom_gradient=True), + ), ) @@ -31,7 +37,7 @@ "SimulatorClass", ( pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -59,7 +65,7 @@ def test_squeezing_probabilities(SimulatorClass): "SimulatorClass", ( pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) diff --git a/tests/backends/test_backend_equivalence.py b/tests/backends/test_backend_equivalence.py index db20869c..6eefca48 100644 --- a/tests/backends/test_backend_equivalence.py +++ b/tests/backends/test_backend_equivalence.py @@ -35,9 +35,15 @@ def is_proportional(first, second): return np.allclose(first, proportion * second) -TensorflowPureFockSimulator = partial( - pq.PureFockSimulator, - calculator=pq.TensorflowCalculator(), +tf_purefock_simulators = ( + partial( + pq.PureFockSimulator, + calculator=pq.TensorflowCalculator(), + ), + partial( + pq.PureFockSimulator, + calculator=pq.TensorflowCalculator(no_custom_gradient=True), + ), ) @@ -46,7 +52,7 @@ def is_proportional(first, second): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -70,7 +76,7 @@ def test_fock_probabilities_should_be_numpy_array_of_floats(SimulatorClass): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -148,7 +154,7 @@ def test_density_matrix_with_squeezed_state(): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -198,7 +204,7 @@ def test_fock_probabilities_with_displaced_state(SimulatorClass): ( pq.PureFockSimulator, pq.FockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.GaussianSimulator, ), ) @@ -237,7 +243,7 @@ def test_Displacement_equivalence_on_multiple_modes(SimulatorClass): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -288,7 +294,7 @@ def test_fock_probabilities_with_displaced_state_with_beamsplitter(SimulatorClas ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -339,7 +345,7 @@ def test_fock_probabilities_with_squeezed_state_with_beamsplitter(SimulatorClass ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -369,7 +375,7 @@ def test_fock_probabilities_with_two_single_mode_squeezings(SimulatorClass): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -397,7 +403,7 @@ def test_Squeezing_equivalence_on_multiple_modes(SimulatorClass): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -452,7 +458,7 @@ def test_fock_probabilities_with_two_mode_squeezing(SimulatorClass): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -503,7 +509,7 @@ def test_fock_probabilities_with_two_mode_squeezing_and_beamsplitter(SimulatorCl ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -551,7 +557,7 @@ def test_fock_probabilities_with_quadratic_phase(SimulatorClass): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -599,7 +605,7 @@ def test_fock_probabilities_with_position_displacement(SimulatorClass): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -647,7 +653,7 @@ def test_fock_probabilities_with_momentum_displacement(SimulatorClass): ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -676,7 +682,7 @@ def test_fock_probabilities_with_position_displacement_is_HBAR_independent( ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -705,7 +711,7 @@ def test_fock_probabilities_with_momentum_displacement_is_HBAR_independent( ( pq.GaussianSimulator, pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, pq.FockSimulator, ), ) @@ -1164,7 +1170,7 @@ def test_fidelity_for_nondisplaced_mixed_states_on_3_modes(SimulatorClass): ( pq.PureFockSimulator, pq.FockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, ), ) def test_cubic_phase_equivalency(SimulatorClass): @@ -1193,7 +1199,7 @@ def test_cubic_phase_equivalency(SimulatorClass): ( pq.PureFockSimulator, pq.FockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, ), ) def test_CubicPhase_equivalence_on_multiple_modes(SimulatorClass): @@ -1231,7 +1237,7 @@ def test_CubicPhase_equivalence_on_multiple_modes(SimulatorClass): ( pq.PureFockSimulator, pq.FockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, ), ) def test_Kerr_gate_leaves_fock_probabilities_invariant(SimulatorClass): @@ -1257,7 +1263,7 @@ def test_Kerr_gate_leaves_fock_probabilities_invariant(SimulatorClass): "SimulatorClass", ( pq.PureFockSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, ), ) def test_Kerr_equivalence(SimulatorClass): @@ -1460,7 +1466,7 @@ def test_Attenuator_raises_InvalidParam_for_non_zero_mean_thermal_excitation( pq.PureFockSimulator, pq.FockSimulator, pq.GaussianSimulator, - TensorflowPureFockSimulator, + *tf_purefock_simulators, ), ) def test_Interferometer_smaller_than_system_size(SimulatorClass):