1#   Licensed under the Apache License, Version 2.0 (the "License");
2#   you may not use this file except in compliance with the License.
3#   You may obtain a copy of the License at
4#
5#       http://www.apache.org/licenses/LICENSE-2.0
6#
7#   Unless required by applicable law or agreed to in writing, software
8#   distributed under the License is distributed on an "AS IS" BASIS,
9#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10#   See the License for the specific language governing permissions and
11#   limitations under the License.
12
13import itertools
14from typing import cast, Tuple
15
16import numpy as np
17import pytest
18import scipy.linalg as la
19import sympy
20import cirq
21import cirq.contrib.acquaintance as cca
22
23import openfermion
24from openfermion.circuits.gates.fermionic_simulation import (
25    sum_of_interaction_operator_gate_generators,
26    state_swap_eigen_component,
27)
28
29
30def test_state_swap_eigen_component_args():
31    with pytest.raises(TypeError):
32        state_swap_eigen_component(0, '12', 1)
33    with pytest.raises(ValueError):
34        state_swap_eigen_component('01', '01', 1)
35    with pytest.raises(ValueError):
36        state_swap_eigen_component('01', '10', 0)
37    with pytest.raises(ValueError):
38        state_swap_eigen_component('01', '100', 1)
39    with pytest.raises(ValueError):
40        state_swap_eigen_component('01', 'ab', 1)
41
42
43@pytest.mark.parametrize('index_pair,n_qubits', [
44    ((0, 1), 2),
45    ((0, 3), 2),
46])
47def test_state_swap_eigen_component(index_pair, n_qubits):
48    state_pair = tuple(format(i, '0' + str(n_qubits) + 'b') for i in index_pair)
49    i, j = index_pair
50    dim = 2**n_qubits
51    for sign in (-1, 1):
52        actual_component = state_swap_eigen_component(state_pair[0],
53                                                      state_pair[1], sign)
54        expected_component = np.zeros((dim, dim))
55        expected_component[i, i] = expected_component[j, j] = 0.5
56        expected_component[i, j] = expected_component[j, i] = sign * 0.5
57        assert np.allclose(actual_component, expected_component)
58
59
60@pytest.mark.parametrize('n_modes, seed',
61                         [(7, np.random.randint(1 << 30)) for _ in range(2)])
62def test_interaction_operator_interconversion(n_modes, seed):
63    operator = openfermion.random_interaction_operator(n_modes,
64                                                       real=False,
65                                                       seed=seed)
66    gates = openfermion.fermionic_simulation_gates_from_interaction_operator(
67        operator)
68    other_operator = sum_of_interaction_operator_gate_generators(n_modes, gates)
69    operator = openfermion.normal_ordered(operator)
70    other_operator = openfermion.normal_ordered(other_operator)
71    assert operator == other_operator
72
73
74def test_interaction_operator_from_bad_gates():
75    for gates in [{(): 'bad'}, {(0,): cirq.X}]:
76        with pytest.raises(TypeError):
77            sum_of_interaction_operator_gate_generators(5, gates)
78
79
80def random_real(size=None, mag=20):
81    return np.random.uniform(-mag, mag, size)
82
83
84def random_complex(size=None, mag=20):
85    return random_real(size, mag) + 1j * random_real(size, mag)
86
87
88def random_fermionic_simulation_gate(order):
89    exponent = random_real()
90    if order == 2:
91        weights = (random_complex(), random_real())
92        return openfermion.QuadraticFermionicSimulationGate(weights,
93                                                            exponent=exponent)
94    weights = random_complex(3)
95    if order == 3:
96        return openfermion.CubicFermionicSimulationGate(weights,
97                                                        exponent=exponent)
98    if order == 4:
99        return openfermion.QuarticFermionicSimulationGate(weights,
100                                                          exponent=exponent)
101
102
103def assert_symbolic_decomposition_consistent(gate):
104    expected_unitary = cirq.unitary(gate)
105
106    weights = tuple(sympy.Symbol(f'w{i}') for i in range(gate.num_weights()))
107    exponent = sympy.Symbol('t')
108    symbolic_gate = type(gate)(weights, exponent=exponent)
109    qubits = cirq.LineQubit.range(gate.num_qubits())
110    circuit = cirq.Circuit(symbolic_gate._decompose_(qubits))
111    resolver = {'t': gate.exponent}
112    for i, w in enumerate(gate.weights):
113        resolver[f'w{i}'] = w
114    resolved_circuit = cirq.resolve_parameters(circuit, resolver)
115    decomp_unitary = resolved_circuit.unitary(qubit_order=qubits)
116
117    assert np.allclose(expected_unitary, decomp_unitary)
118
119
120def assert_generators_consistent(gate):
121    qubit_generator = gate.qubit_generator_matrix
122    qubit_generator_from_fermion_generator = (super(
123        type(gate), gate).qubit_generator_matrix)
124    assert np.allclose(qubit_generator, qubit_generator_from_fermion_generator)
125
126
127def assert_resolution_consistent(gate):
128    weight_names = [f'w{i}' for i in range(gate.num_weights())]
129    weight_params = [sympy.Symbol(w) for w in weight_names]
130    resolver = dict(zip(weight_names, gate.weights))
131    resolver['s'] = gate._global_shift
132    resolver['e'] = gate._exponent
133    symbolic_gate = type(gate)(weight_params,
134                               exponent=sympy.Symbol('e'),
135                               global_shift=sympy.Symbol('s'))
136    resolved_gate = cirq.resolve_parameters(symbolic_gate, resolver)
137    assert resolved_gate == gate
138
139
140def assert_fswap_consistent(gate):
141    gate = gate.__copy__()
142    n_qubits = gate.num_qubits()
143    for i in range(n_qubits - 1):
144        fswap = cirq.kron(np.eye(1 << i), cirq.unitary(openfermion.FSWAP),
145                          np.eye(1 << (n_qubits - i - 2)))
146        assert fswap.shape == (1 << n_qubits,) * 2
147        generator = gate.qubit_generator_matrix
148        fswapped_generator = np.linalg.multi_dot([fswap, generator, fswap])
149        gate.fswap(i)
150        assert np.allclose(gate.qubit_generator_matrix, fswapped_generator)
151    for i in (-1, n_qubits):
152        with pytest.raises(ValueError):
153            gate.fswap(i)
154
155
156def assert_permute_consistent(gate):
157    gate = gate.__copy__()
158    n_qubits = gate.num_qubits()
159    qubits = cirq.LineQubit.range(n_qubits)
160    for pos in itertools.permutations(range(n_qubits)):
161        permuted_gate = gate.__copy__()
162        gate.permute(pos)
163        assert permuted_gate.permuted(pos) == gate
164        actual_unitary = cirq.unitary(permuted_gate)
165
166        ops = [
167            cca.LinearPermutationGate(n_qubits, dict(zip(range(n_qubits), pos)),
168                                      openfermion.FSWAP)(*qubits),
169            gate(*qubits),
170            cca.LinearPermutationGate(n_qubits, dict(zip(pos, range(n_qubits))),
171                                      openfermion.FSWAP)(*qubits)
172        ]
173        circuit = cirq.Circuit(ops)
174        expected_unitary = cirq.unitary(circuit)
175        assert np.allclose(actual_unitary, expected_unitary)
176
177    with pytest.raises(ValueError):
178        gate.permute(range(1, n_qubits))
179    with pytest.raises(ValueError):
180        gate.permute([1] * n_qubits)
181
182
183def assert_interaction_operator_consistent(gate):
184    interaction_op = gate.interaction_operator_generator()
185    other_gate = gate.from_interaction_operator(operator=interaction_op)
186    if other_gate is None:
187        assert np.allclose(gate.weights, 0)
188    else:
189        assert cirq.approx_eq(gate, other_gate)
190    interaction_op = openfermion.normal_ordered(interaction_op)
191    other_interaction_op = openfermion.InteractionOperator.zero(
192        interaction_op.n_qubits)
193    super(type(gate),
194          gate).interaction_operator_generator(operator=other_interaction_op)
195    other_interaction_op = openfermion.normal_ordered(interaction_op)
196    assert interaction_op == other_interaction_op
197
198    other_interaction_op = super(type(gate),
199                                 gate).interaction_operator_generator()
200    other_interaction_op = openfermion.normal_ordered(interaction_op)
201    assert interaction_op == other_interaction_op
202
203
204random_quadratic_gates = [random_fermionic_simulation_gate(2) for _ in range(5)]
205manual_quadratic_gates = [
206    openfermion.QuadraticFermionicSimulationGate(weights)
207    for weights in [cast(Tuple[float, float], (1, 1)), (1, 0), (0, 1), (0, 0)]
208]
209quadratic_gates = random_quadratic_gates + manual_quadratic_gates
210cubic_gates = ([openfermion.CubicFermionicSimulationGate()] +
211               [random_fermionic_simulation_gate(3) for _ in range(5)])
212quartic_gates = ([openfermion.QuarticFermionicSimulationGate()] +
213                 [random_fermionic_simulation_gate(4) for _ in range(5)])
214gates = quadratic_gates + cubic_gates + quartic_gates
215
216
217@pytest.mark.parametrize('gate', gates)
218def test_fermionic_simulation_gate(gate):
219    openfermion.testing.assert_implements_consistent_protocols(gate)
220
221    generator = gate.qubit_generator_matrix
222    expected_unitary = la.expm(-1j * gate.exponent * generator)
223    actual_unitary = cirq.unitary(gate)
224    assert np.allclose(expected_unitary, actual_unitary)
225
226    assert_fswap_consistent(gate)
227    assert_permute_consistent(gate)
228    assert_generators_consistent(gate)
229    assert_resolution_consistent(gate)
230    assert_interaction_operator_consistent(gate)
231
232    assert gate.num_weights() == super(type(gate), gate).num_weights()
233
234
235@pytest.mark.parametrize('weights', list(np.random.rand(10, 3)) + [(1, 0, 1)])
236def test_weights_and_exponent(weights):
237    exponents = np.linspace(-1, 1, 8)
238    gates = tuple(
239        openfermion.QuarticFermionicSimulationGate(
240            weights / exponent, exponent=exponent, absorb_exponent=True)
241        for exponent in exponents)
242
243    for g1, g2 in itertools.combinations(gates, 2):
244        assert cirq.approx_eq(g1, g2, atol=1e-100)
245
246    for i, (gate, exponent) in enumerate(zip(gates, exponents)):
247        assert gate.exponent == 1
248        new_exponent = exponents[-i]
249        new_gate = gate._with_exponent(new_exponent)
250        assert new_gate.exponent == new_exponent
251
252
253def test_zero_weights():
254    for gate_type in [
255            openfermion.QuadraticFermionicSimulationGate,
256            openfermion.CubicFermionicSimulationGate,
257            openfermion.QuarticFermionicSimulationGate
258    ]:
259        weights = (0,) * gate_type.num_weights()
260        gate = gate_type(weights)
261        n_qubits = gate.num_qubits()
262
263        assert np.allclose(cirq.unitary(gate), np.eye(2**n_qubits))
264        cirq.testing.assert_decompose_is_consistent_with_unitary(gate)
265
266        operator = openfermion.InteractionOperator.zero(n_qubits)
267        assert gate_type.from_interaction_operator(operator=operator) is None
268
269
270@pytest.mark.parametrize(
271    'weights,exponent',
272    [((np.random.uniform(-5, 5) + 1j * np.random.uniform(-5, 5),
273       np.random.uniform(-5, 5)), np.random.uniform(-5, 5)) for _ in range(5)])
274def test_quadratic_fermionic_simulation_gate_unitary(weights, exponent):
275    generator = np.zeros((4, 4), dtype=np.complex128)
276    # w0 |10><01| + h.c.
277    generator[2, 1] = weights[0]
278    generator[1, 2] = weights[0].conjugate()
279    # w1 |11><11|
280    generator[3, 3] = weights[1]
281    expected_unitary = la.expm(-1j * exponent * generator)
282
283    gate = openfermion.QuadraticFermionicSimulationGate(weights,
284                                                        exponent=exponent)
285    actual_unitary = cirq.unitary(gate)
286
287    assert np.allclose(expected_unitary, actual_unitary)
288
289    symbolic_gate = (openfermion.QuadraticFermionicSimulationGate(
290        (sympy.Symbol('w0'), sympy.Symbol('w1')), exponent=sympy.Symbol('t')))
291    qubits = cirq.LineQubit.range(2)
292    circuit = cirq.Circuit(symbolic_gate._decompose_(qubits))
293    resolver = {'w0': weights[0], 'w1': weights[1], 't': exponent}
294    resolved_circuit = cirq.resolve_parameters(circuit, resolver)
295    decomp_unitary = resolved_circuit.unitary(qubit_order=qubits)
296
297    assert np.allclose(expected_unitary, decomp_unitary)
298
299
300@pytest.mark.parametrize('gate', random_quadratic_gates)
301def test_quadratic_fermionic_simulation_gate_symbolic_decompose(gate):
302    assert_symbolic_decomposition_consistent(gate)
303
304
305def test_cubic_fermionic_simulation_gate_equality():
306    eq = cirq.testing.EqualsTester()
307    eq.add_equality_group(
308        openfermion.CubicFermionicSimulationGate()**0.5,
309        openfermion.CubicFermionicSimulationGate((1,) * 3, exponent=0.5),
310        openfermion.CubicFermionicSimulationGate((0.5,) * 3),
311    )
312    eq.add_equality_group(openfermion.CubicFermionicSimulationGate((1j, 0, 0)),)
313    eq.add_equality_group(
314        openfermion.CubicFermionicSimulationGate((sympy.Symbol('s'), 0, 0),
315                                                 exponent=2),
316        openfermion.CubicFermionicSimulationGate((2 * sympy.Symbol('s'), 0, 0),
317                                                 exponent=1),
318    )
319    eq.add_equality_group(
320        openfermion.CubicFermionicSimulationGate((0, 0.7, 0), global_shift=2),
321        openfermion.CubicFermionicSimulationGate((0, 0.35, 0),
322                                                 global_shift=1,
323                                                 exponent=2),
324    )
325    eq.add_equality_group(
326        openfermion.CubicFermionicSimulationGate((1, 1, 1)),
327        openfermion.CubicFermionicSimulationGate(((1 + 2 * np.pi), 1, 1)),
328    )
329
330
331@pytest.mark.parametrize('exponent,control',
332                         itertools.product([0, 1, -1, 0.25, -0.5, 0.1],
333                                           [0, 1, 2]))
334def test_cubic_fermionic_simulation_gate_consistency_special(exponent, control):
335    weights = tuple(np.eye(1, 3, control)[0] * 0.5 * np.pi)
336    general_gate = openfermion.CubicFermionicSimulationGate(weights,
337                                                            exponent=exponent)
338    general_unitary = cirq.unitary(general_gate)
339
340    indices = np.dot(list(itertools.product((0, 1), repeat=3)),
341                     (2**np.roll(np.arange(3), -control))[::-1])
342    special_gate = cirq.ControlledGate(cirq.ISWAP**-exponent)
343    special_unitary = (
344        cirq.unitary(special_gate)[indices[:, np.newaxis], indices])
345
346    assert np.allclose(general_unitary, special_unitary)
347
348
349@pytest.mark.parametrize(
350    'weights,exponent',
351    [(np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3),
352      np.random.uniform(-5, 5)) for _ in range(5)])
353def test_cubic_fermionic_simulation_gate_consistency_docstring(
354        weights, exponent):
355    generator = np.zeros((8, 8), dtype=np.complex128)
356    # w0 |110><101| + h.c.
357    generator[6, 5] = weights[0]
358    generator[5, 6] = weights[0].conjugate()
359    # w1 |110><011| + h.c.
360    generator[6, 3] = weights[1]
361    generator[3, 6] = weights[1].conjugate()
362    # w2 |101><011| + h.c.
363    generator[5, 3] = weights[2]
364    generator[3, 5] = weights[2].conjugate()
365    expected_unitary = la.expm(-1j * exponent * generator)
366
367    gate = openfermion.CubicFermionicSimulationGate(weights, exponent=exponent)
368    actual_unitary = cirq.unitary(gate)
369
370    assert np.allclose(expected_unitary, actual_unitary)
371
372
373def test_quartic_fermionic_simulation_consistency():
374    openfermion.testing.assert_implements_consistent_protocols(
375        openfermion.QuarticFermionicSimulationGate())
376
377
378quartic_fermionic_simulation_simulator_test_cases = [
379    (openfermion.QuarticFermionicSimulationGate(
380        (0, 0, 0)), 1., np.ones(16) / 4., np.ones(16) / 4., 5e-6),
381    (openfermion.QuarticFermionicSimulationGate((0.2, -0.1, 0.7)), 0.,
382     np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.,
383     np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.,
384     5e-6),
385    (openfermion.QuarticFermionicSimulationGate((0.2, -0.1, 0.7)), 0.3,
386     np.array([1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) / 4.,
387     np.array([
388         1, -1, -1, -np.exp(0.21j), -1, -np.exp(-0.03j),
389         np.exp(-0.06j), 1, 1,
390         np.exp(-0.06j),
391         np.exp(-0.03j), 1,
392         np.exp(0.21j), 1, 1, 1
393     ]) / 4., 5e-6),
394    (openfermion.QuarticFermionicSimulationGate((1. / 3, 0, 0)), 1.,
395     np.array([0, 0, 0, 0, 0, 0, 1., 0, 0, 1., 0, 0, 0, 0, 0, 0]) / np.sqrt(2),
396     np.array([0, 0, 0, 0, 0, 0, 1., 0, 0, 1., 0, 0, 0, 0, 0, 0]) / np.sqrt(2),
397     5e-6),
398    (openfermion.QuarticFermionicSimulationGate((0, np.pi / 3, 0)), 1.,
399     np.array([1., 1., 0, 0, 0, 1., 0, 0, 0, 0., -1., 0, 0, 0, 0, 0]) / 2.,
400     np.array([
401         1., 1., 0, 0, 0, -np.exp(4j * np.pi / 3), 0, 0, 0, 0.,
402         -np.exp(1j * np.pi / 3), 0, 0, 0, 0, 0
403     ]) / 2., 5e-6),
404    (openfermion.QuarticFermionicSimulationGate((0, 0, -np.pi / 2)), 1.,
405     np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1., 0, 0,
406               0]), np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
407                              0]), 5e-6),
408    (openfermion.QuarticFermionicSimulationGate((0, 0, -0.25 * np.pi)), 1.,
409     np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
410     np.array([0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1j, 0, 0, 0]) / np.sqrt(2),
411     5e-6),
412    (openfermion.QuarticFermionicSimulationGate(
413        (-np.pi / 4, np.pi / 6, -np.pi / 2)), 1.,
414     np.array([0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0]) / np.sqrt(3),
415     np.array([
416         0, 0, 0, 1j, 0, -1j / 2., 1 / np.sqrt(2), 0, 0, 1j / np.sqrt(2),
417         np.sqrt(3) / 2, 0, 0, 0, 0, 0
418     ]) / np.sqrt(3), 5e-6),
419]
420
421
422@pytest.mark.parametrize('gate, exponent, initial_state, correct_state, atol',
423                         quartic_fermionic_simulation_simulator_test_cases)
424def test_quartic_fermionic_simulation_on_simulator(gate, exponent,
425                                                   initial_state, correct_state,
426                                                   atol):
427
428    a, b, c, d = cirq.LineQubit.range(4)
429    circuit = cirq.Circuit(gate(a, b, c, d)**exponent)
430    result = circuit.final_state_vector(initial_state=initial_state)
431    cirq.testing.assert_allclose_up_to_global_phase(result,
432                                                    correct_state,
433                                                    atol=atol)
434
435
436def test_quartic_fermionic_simulation_eq():
437    eq = cirq.testing.EqualsTester()
438
439    eq.add_equality_group(
440        openfermion.QuarticFermionicSimulationGate((1.2, 0.4, -0.4),
441                                                   exponent=0.5),
442        openfermion.QuarticFermionicSimulationGate((0.3, 0.1, -0.1),
443                                                   exponent=2),
444        openfermion.QuarticFermionicSimulationGate((-0.6, -0.2, 0.2),
445                                                   exponent=-1),
446        openfermion.QuarticFermionicSimulationGate((0.6, 0.2, 2 * np.pi - 0.2)),
447    )
448
449    eq.add_equality_group(
450        openfermion.QuarticFermionicSimulationGate((-0.6, 0.0, 0.3),
451                                                   exponent=0.5))
452
453    eq.make_equality_group(lambda: openfermion.QuarticFermionicSimulationGate(
454        (0.1, -0.3, 0.0), exponent=0.0))
455    eq.make_equality_group(lambda: openfermion.QuarticFermionicSimulationGate(
456        (1., -1., 0.5), exponent=0.75))
457
458
459def test_quadratic_fermionic_simulation_gate_text_diagram():
460    gate = openfermion.QuadraticFermionicSimulationGate((1, 1))
461    a, b, c = cirq.LineQubit.range(3)
462    circuit = cirq.Circuit([gate(a, b), gate(b, c)])
463
464    assert super(type(gate), gate).wire_symbol(False) == type(gate).__name__
465    assert (super(type(gate), gate)._diagram_exponent(
466        cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) == gate._exponent)
467
468    expected_text_diagram = """
4690: ───↓↑(1, 1)──────────────
4704711: ───↓↑─────────↓↑(1, 1)───
4724732: ──────────────↓↑─────────
474""".strip()
475    cirq.testing.assert_has_diagram(circuit, expected_text_diagram)
476
477    expected_text_diagram = """
4780: ---a*a(1, 1)---------------
479      |
4801: ---a*a---------a*a(1, 1)---
481                  |
4822: ---------------a*a---------
483""".strip()
484    cirq.testing.assert_has_diagram(circuit,
485                                    expected_text_diagram,
486                                    use_unicode_characters=False)
487
488
489def test_cubic_fermionic_simulation_gate_text_diagram():
490    gate = openfermion.CubicFermionicSimulationGate((1, 1, 1))
491    qubits = cirq.LineQubit.range(5)
492    circuit = cirq.Circuit([gate(*qubits[:3]), gate(*qubits[2:5])])
493
494    assert super(type(gate), gate).wire_symbol(False) == type(gate).__name__
495    assert (super(type(gate), gate)._diagram_exponent(
496        cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) == gate._exponent)
497
498    expected_text_diagram = """
4990: ───↕↓↑(1, 1, 1)──────────────────
5005011: ───↕↓↑───────────────────────────
5025032: ───↕↓↑────────────↕↓↑(1, 1, 1)───
5045053: ──────────────────↕↓↑────────────
5065074: ──────────────────↕↓↑────────────
508""".strip()
509    cirq.testing.assert_has_diagram(circuit, expected_text_diagram)
510
511    expected_text_diagram = """
5120: ---na*a(1, 1, 1)-------------------
513      |
5141: ---na*a----------------------------
515      |
5162: ---na*a------------na*a(1, 1, 1)---
517                      |
5183: -------------------na*a------------
519                      |
5204: -------------------na*a------------
521""".strip()
522    cirq.testing.assert_has_diagram(circuit,
523                                    expected_text_diagram,
524                                    use_unicode_characters=False)
525
526
527test_weights = [1.0, 0.5, 0.25, 0.1, 0.0, -0.5]
528
529
530@pytest.mark.parametrize('weights',
531                         itertools.chain(
532                             itertools.product(test_weights, repeat=3),
533                             np.random.rand(10, 3)))
534def test_quartic_fermionic_simulation_decompose(weights):
535    cirq.testing.assert_decompose_is_consistent_with_unitary(
536        openfermion.QuarticFermionicSimulationGate(weights))
537
538
539@pytest.mark.parametrize(
540    'weights,exponent',
541    [(np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3),
542      np.random.uniform(-5, 5)) for _ in range(5)])
543def test_quartic_fermionic_simulation_unitary(weights, exponent):
544    generator = np.zeros((1 << 4,) * 2, dtype=np.complex128)
545
546    # w0 |1001><0110| + h.c.
547    generator[9, 6] = weights[0]
548    generator[6, 9] = weights[0].conjugate()
549    # w1 |1010><0101| + h.c.
550    generator[10, 5] = weights[1]
551    generator[5, 10] = weights[1].conjugate()
552    # w2 |1100><0011| + h.c.
553    generator[12, 3] = weights[2]
554    generator[3, 12] = weights[2].conjugate()
555    expected_unitary = la.expm(-1j * exponent * generator)
556
557    gate = openfermion.QuarticFermionicSimulationGate(weights,
558                                                      exponent=exponent)
559    actual_unitary = cirq.unitary(gate)
560
561    assert np.allclose(expected_unitary, actual_unitary)
562
563
564def test_quartic_fermionic_simulation_gate_text_diagram():
565    gate = openfermion.QuarticFermionicSimulationGate((1, 1, 1))
566    qubits = cirq.LineQubit.range(6)
567    circuit = cirq.Circuit([gate(*qubits[:4]), gate(*qubits[-4:])])
568
569    assert super(type(gate), gate).wire_symbol(False) == type(gate).__name__
570    for G in (gate, gate._with_exponent('e')):
571        assert (super(type(G), G)._diagram_exponent(
572            cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) == G._exponent)
573
574    expected_text_diagram = """
5750: ───⇊⇈(1, 1, 1)─────────────────
5765771: ───⇊⇈──────────────────────────
5785792: ───⇊⇈────────────⇊⇈(1, 1, 1)───
580      │             │
5813: ───⇊⇈────────────⇊⇈────────────
5825834: ─────────────────⇊⇈────────────
5845855: ─────────────────⇊⇈────────────
586""".strip()
587    cirq.testing.assert_has_diagram(circuit, expected_text_diagram)
588
589    expected_text_diagram = """
5900: ---a*a*aa(1, 1, 1)---------------------
591      |
5921: ---a*a*aa------------------------------
593      |
5942: ---a*a*aa------------a*a*aa(1, 1, 1)---
595      |                 |
5963: ---a*a*aa------------a*a*aa------------
597                        |
5984: ---------------------a*a*aa------------
599                        |
6005: ---------------------a*a*aa------------
601""".strip()
602    cirq.testing.assert_has_diagram(circuit,
603                                    expected_text_diagram,
604                                    use_unicode_characters=False)
605
606
607@pytest.mark.parametrize(
608    'weights,exponent',
609    [(np.random.uniform(-5, 5, 3) + 1j * np.random.uniform(-5, 5, 3),
610      np.random.uniform(-5, 5)) for _ in range(5)])
611def test_quartic_fermionic_simulation_apply_unitary(weights, exponent):
612    gate = openfermion.QuarticFermionicSimulationGate(weights,
613                                                      exponent=exponent)
614    cirq.testing.assert_has_consistent_apply_unitary(gate, atol=5e-6)
615