def random_fermionic_simulation_gate(order):
    exponent = random_real()
    if order == 2:
        weights = (random_complex(), random_real())
        return openfermion.QuadraticFermionicSimulationGate(weights,
                                                            exponent=exponent)
    weights = random_complex(3)
    if order == 3:
        return openfermion.CubicFermionicSimulationGate(weights,
                                                        exponent=exponent)
    if order == 4:
        return openfermion.QuarticFermionicSimulationGate(weights,
                                                          exponent=exponent)
def test_weights_and_exponent(weights):
    exponents = np.linspace(-1, 1, 8)
    gates = tuple(
        openfermion.QuarticFermionicSimulationGate(
            weights / exponent, exponent=exponent, absorb_exponent=True)
        for exponent in exponents)

    for g1, g2 in itertools.combinations(gates, 2):
        assert cirq.approx_eq(g1, g2, atol=1e-100)

    for i, (gate, exponent) in enumerate(zip(gates, exponents)):
        assert gate.exponent == 1
        new_exponent = exponents[-i]
        new_gate = gate._with_exponent(new_exponent)
        assert new_gate.exponent == new_exponent
def test_quartic_fermionic_simulation_unitary(weights, exponent):
    generator = np.zeros((1 << 4,) * 2, dtype=np.complex128)

    # w0 |1001><0110| + h.c.
    generator[9, 6] = weights[0]
    generator[6, 9] = weights[0].conjugate()
    # w1 |1010><0101| + h.c.
    generator[10, 5] = weights[1]
    generator[5, 10] = weights[1].conjugate()
    # w2 |1100><0011| + h.c.
    generator[12, 3] = weights[2]
    generator[3, 12] = weights[2].conjugate()
    expected_unitary = la.expm(-1j * exponent * generator)

    gate = openfermion.QuarticFermionicSimulationGate(weights,
                                                      exponent=exponent)
    actual_unitary = cirq.unitary(gate)

    assert np.allclose(expected_unitary, actual_unitary)
def test_quartic_fermionic_simulation_gate_text_diagram():
    gate = openfermion.QuarticFermionicSimulationGate((1, 1, 1))
    qubits = cirq.LineQubit.range(6)
    circuit = cirq.Circuit([gate(*qubits[:4]), gate(*qubits[-4:])])

    assert super(type(gate), gate).wire_symbol(False) == type(gate).__name__
    for G in (gate, gate._with_exponent('e')):
        assert (super(type(G), G)._diagram_exponent(
            cirq.CircuitDiagramInfoArgs.UNINFORMED_DEFAULT) == G._exponent)

    expected_text_diagram = """
0: ───⇊⇈(1, 1, 1)─────────────────
      │
1: ───⇊⇈──────────────────────────
      │
2: ───⇊⇈────────────⇊⇈(1, 1, 1)───
      │             │
3: ───⇊⇈────────────⇊⇈────────────
                    │
4: ─────────────────⇊⇈────────────
                    │
5: ─────────────────⇊⇈────────────
""".strip()
    cirq.testing.assert_has_diagram(circuit, expected_text_diagram)

    expected_text_diagram = """
0: ---a*a*aa(1, 1, 1)---------------------
      |
1: ---a*a*aa------------------------------
      |
2: ---a*a*aa------------a*a*aa(1, 1, 1)---
      |                 |
3: ---a*a*aa------------a*a*aa------------
                        |
4: ---------------------a*a*aa------------
                        |
5: ---------------------a*a*aa------------
""".strip()
    cirq.testing.assert_has_diagram(circuit,
                                    expected_text_diagram,
                                    use_unicode_characters=False)
def test_quartic_fermionic_simulation_eq():
    eq = cirq.testing.EqualsTester()

    eq.add_equality_group(
        openfermion.QuarticFermionicSimulationGate((1.2, 0.4, -0.4),
                                                   exponent=0.5),
        openfermion.QuarticFermionicSimulationGate((0.3, 0.1, -0.1),
                                                   exponent=2),
        openfermion.QuarticFermionicSimulationGate((-0.6, -0.2, 0.2),
                                                   exponent=-1),
        openfermion.QuarticFermionicSimulationGate((0.6, 0.2, 2 * np.pi - 0.2)),
    )

    eq.add_equality_group(
        openfermion.QuarticFermionicSimulationGate((-0.6, 0.0, 0.3),
                                                   exponent=0.5))

    eq.make_equality_group(lambda: openfermion.QuarticFermionicSimulationGate(
        (0.1, -0.3, 0.0), exponent=0.0))
    eq.make_equality_group(lambda: openfermion.QuarticFermionicSimulationGate(
        (1., -1., 0.5), exponent=0.75))
def test_quartic_fermionic_simulation_apply_unitary(weights, exponent):
    gate = openfermion.QuarticFermionicSimulationGate(weights,
                                                      exponent=exponent)
    cirq.testing.assert_has_consistent_apply_unitary(gate, atol=5e-6)
def test_quartic_fermionic_simulation_decompose(weights):
    cirq.testing.assert_decompose_is_consistent_with_unitary(
        openfermion.QuarticFermionicSimulationGate(weights))
def test_quartic_fermionic_simulation_consistency():
    openfermion.testing.assert_implements_consistent_protocols(
        openfermion.QuarticFermionicSimulationGate())
    other_interaction_op = super(type(gate),
                                 gate).interaction_operator_generator()
    other_interaction_op = openfermion.normal_ordered(interaction_op)
    assert interaction_op == other_interaction_op


random_quadratic_gates = [random_fermionic_simulation_gate(2) for _ in range(5)]
manual_quadratic_gates = [
    openfermion.QuadraticFermionicSimulationGate(weights)
    for weights in [cast(Tuple[float, float], (1, 1)), (1, 0), (0, 1), (0, 0)]
]
quadratic_gates = random_quadratic_gates + manual_quadratic_gates
cubic_gates = ([openfermion.CubicFermionicSimulationGate()] +
               [random_fermionic_simulation_gate(3) for _ in range(5)])
quartic_gates = ([openfermion.QuarticFermionicSimulationGate()] +
                 [random_fermionic_simulation_gate(4) for _ in range(5)])
gates = quadratic_gates + cubic_gates + quartic_gates


@pytest.mark.parametrize('gate', gates)
def test_fermionic_simulation_gate(gate):
    openfermion.testing.assert_implements_consistent_protocols(gate)

    generator = gate.qubit_generator_matrix
    expected_unitary = la.expm(-1j * gate.exponent * generator)
    actual_unitary = cirq.unitary(gate)
    assert np.allclose(expected_unitary, actual_unitary)

    assert_fswap_consistent(gate)
    assert_permute_consistent(gate)