# install latest version
!pip install cirq==0.5 --quiet

# Alternatively, install directly from HEAD on github:
# !pip install git+https://github.com/quantumlib/Cirq.git --quiet

import cirq
import numpy as np
import matplotlib 
print(cirq.google.Bristlecone)

a = cirq.NamedQubit("a")
b = cirq.NamedQubit("b")
c = cirq.NamedQubit("c")
ops = [cirq.H(a), cirq.H(b), cirq.CNOT(b, c), cirq.H(b)]
circuit = cirq.Circuit.from_ops(ops)
print(circuit)

cirq.unitary(cirq.H)

for i, moment in enumerate(circuit):
    print('Moment {}: {}'.format(i, moment))

print(repr(circuit))

def xor_swap(a, b):
    yield cirq.CNOT(a, b)
    yield cirq.CNOT(b, a)
    yield cirq.CNOT(a, b)
  
def left_rotate(qubits):
    for i in range(len(qubits) - 1):
        a, b = qubits[i:i+2]
        yield xor_swap(a, b)

line = cirq.LineQubit.range(5)
print(cirq.Circuit.from_ops(left_rotate(line)))

circuit = cirq.Circuit()
circuit.append([cirq.CZ(a, b)])
circuit.append([cirq.H(a), cirq.H(b), cirq.H(c)])
print(circuit)

circuit = cirq.Circuit()
circuit.append([cirq.CZ(a, b)])
circuit.append([cirq.H(c), cirq.H(b), cirq.H(b), cirq.H(a)], )
print(circuit)

#@title
a = cirq.NamedQubit('a')
b = cirq.NamedQubit('b')
c = cirq.NamedQubit('c')
circuit = cirq.Circuit()
circuit.append([cirq.CZ(a, b), cirq.H(c), cirq.H(a)] )
circuit.append([cirq.H(b), cirq.CZ(b, c), cirq.H(b), cirq.H(a), cirq.H(a)],
               strategy=cirq.InsertStrategy.NEW_THEN_INLINE)
print(circuit)

def basic_circuit(measure=True):
    sqrt_x = cirq.X**0.5
    cz = cirq.CZ
    yield sqrt_x(a), sqrt_x(b)
    yield cz(a, b)
    yield sqrt_x(a), sqrt_x(b)
    if measure:
        yield cirq.measure(a,b)
        
circuit = cirq.Circuit.from_ops(basic_circuit())
print(circuit)

simulator = cirq.Simulator()
circuit = cirq.Circuit.from_ops(basic_circuit())
result = simulator.run(circuit)
print('Measurement results')
print(result)

circuit = cirq.Circuit()
circuit.append(basic_circuit(measure=False))    
result = simulator.simulate(circuit, qubit_order=[a, b])

print('Wavefunction:')
print(np.around(result.final_state, 3))
print('Dirac notation:')
print(result.dirac_notation())

circuit = cirq.Circuit.from_ops(basic_circuit())
result = simulator.run(circuit, repetitions=1000)
print(result.histogram(key='a,b'))

print(result.histogram(key='a,b', fold_func=lambda e: 'agree' if e[0] == e[1] else 'disagree'))

q0, q1 = cirq.LineQubit.range(2)
oracles = {
    '0': [],
    '1': [cirq.X(q1)],
    'x': [cirq.CNOT(q0, q1)],
    'notx': [cirq.CNOT(q0, q1), cirq.X(q1)]
}    

q0, q1 = cirq.LineQubit.range(2)
oracles = {
    '0': [],
    '1': [cirq.X(q1)],
    'x': [cirq.CNOT(q0, q1)],
    'notx': [cirq.CNOT(q0, q1), cirq.X(q1)]
}    

def deutsch_algorithm(oracle):
    yield cirq.X(q1)
    yield cirq.H(q0), cirq.H(q1)
    yield oracle
    yield cirq.H(q0)
    yield cirq.measure(q0)

for key, oracle in oracles.items():
    print('Circuit for {}...'.format(key))
    print('{}\n'.format(cirq.Circuit.from_ops(deutsch_algorithm(oracle))))

simulator = cirq.Simulator()
for key, oracle in oracles.items():
    result = simulator.run(cirq.Circuit.from_ops(deutsch_algorithm(oracle)), 
                          repetitions=10)
    print('oracle: {:<4} results: {}'.format(key, result))

q0, q1, q2 = cirq.LineQubit.range(3)
constant = ([], [cirq.X(q2)])
balanced = ([cirq.CNOT(q0, q2)], [cirq.CNOT(q1, q2)], [cirq.CNOT(q0, q2), cirq.CNOT(q1, q2)],
            [cirq.CNOT(q0, q2), cirq.X(q2)], [ cirq.CNOT(q1, q2), cirq.X(q2)], [cirq.CNOT(q0, q2), cirq.CNOT(q1, q2), cirq.X(q2)])
for i, ops in enumerate(constant):
    print('\nConstant function {}'.format(i))
    print(cirq.Circuit.from_ops(ops).to_text_diagram(qubit_order=[q0, q1, q2]))
    print()

for i, ops in enumerate(balanced):
    print('\nBalanced function {}'.format(i))
    print(cirq.Circuit.from_ops(ops).to_text_diagram(qubit_order=[q0, q1, q2]))
    


simulator = cirq.Simulator()
def your_circuit(oracle):
    # your code here
    yield oracle
    # and here
    yield cirq.measure(q0, q1, q2)

print('Your result on constant functions')
for oracle in constant:
    result = simulator.run(cirq.Circuit.from_ops(your_circuit(oracle)), repetitions=10)
    print(result)
    
print('Your result on balanced functions')
for oracle in balanced:
    result = simulator.run(cirq.Circuit.from_ops(your_circuit(oracle)), repetitions=10)
    print(result)

#@title
simulator = cirq.Simulator()
def your_circuit(oracle):
    # phase kickback trick
    yield cirq.X(q2), cirq.H(q2)
    
    # equal superposition over input bits
    yield cirq.H(q0), cirq.H(q1)
    
    # query the function
    yield oracle
    
    # interference to get result, put last qubit into |1>
    yield cirq.H(q0), cirq.H(q1), cirq.H(q2)
    
    # a final OR gate to put result in final qubit
    yield cirq.X(q0), cirq.X(q1), cirq.CCX(q0, q1, q2)
    yield cirq.measure(q2)
    

print('Your result on constant functions')
for oracle in constant:
    result = simulator.run(cirq.Circuit.from_ops(your_circuit(oracle)), repetitions=10)
    print(result)
    
print('Your result on balanced functions')
for oracle in balanced:
    result = simulator.run(cirq.Circuit.from_ops(your_circuit(oracle)), repetitions=10)
    print(result)

q0, q1, q2 = cirq.LineQubit.range(3)
ops = [
    cirq.X(q0),
    cirq.Y(q1),
    cirq.Z(q2),
    cirq.CZ(q0,q1),
    cirq.CNOT(q1,q2),
    cirq.H(q0),
    cirq.T(q1),
    cirq.S(q2),
    cirq.CCZ(q0, q1, q2),
    cirq.SWAP(q0, q1),
    cirq.CSWAP(q0, q1, q2),
    cirq.CCX(q0, q1, q2),
    cirq.ISWAP(q0, q1),
    cirq.Rx(0.5 * np.pi)(q0),
    cirq.Ry(.5 * np.pi)(q1),
    cirq.Rz(0.5 * np.pi)(q2),
    (cirq.X**0.5)(q0),
]
print(cirq.Circuit.from_ops(ops))

print(cirq.unitary(cirq.CNOT))

print(cirq.unitary(cirq.Rx(0.5 * np.pi)))

a = cirq.NamedQubit('a')
circuit = cirq.Circuit.from_ops([cirq.Rx(np.pi / 50.0)(a) for theta in range(200)])
print('Circuit is a bunch of small rotations about Pauli X axis:')
print('{}\n'.format(circuit))
p0 = []
z = []
print('We step through the circuit and plot the z component of the vector '
      'as a function of index of the moment being stepped over.')
for i, step in enumerate(simulator.simulate_moment_steps(circuit)):
    prob = np.abs(step.state_vector()) ** 2
    z.append(i)
    p0.append(prob[0])
matplotlib.pyplot.style.use('seaborn-whitegrid')
matplotlib.pyplot.plot(z, p0, 'o')

repetitions = 100
a = cirq.NamedQubit('a')
circuit = cirq.Circuit.from_ops([cirq.Rx(np.pi / 50.0)(a) for theta in range(200)])
p0 = []
z = []
for i, step in enumerate(simulator.simulate_moment_steps(circuit)):
    samples = step.sample([a], repetitions=repetitions)
    prob0 = np.sum(samples, axis=0)[0] / repetitions
    p0.append(prob0)
    z.append(i)
matplotlib.pyplot.style.use('seaborn-whitegrid')
matplotlib.pyplot.plot(z, p0, 'o')

class RationalGate(cirq.SingleQubitGate):
    
    def _unitary_(self):
        return np.array([[3 / 5, 4 / 5], [-4 / 5, 3 / 5]])
    
    def __str__(self):
        return 'ζ'


a = cirq.NamedQubit('a')
rg = RationalGate()
print(cirq.Circuit.from_ops([rg(a)]))

print(cirq.unitary(rg))

circuit = cirq.Circuit.from_ops([rg(a)])
simulator = cirq.Simulator()
result = simulator.simulate(circuit)
print(result.final_state)

class CRx(cirq.TwoQubitGate):

    def __init__(self, theta):
        self.theta = theta

    def _unitary_(self):
      return np.array([
        
      ])
    

    
    pass

    # Get this to print nicely in an ASCII circuit, you should also
    # implement the _circuit_diagram_info_ method from the
    # SupportsCircuitDiagramInfo protocol. You can return a tuple
    # of strings from this method.


print(np.around(cirq.unitary(CRx(0.25 * np.pi))))     

# Also get your class to print a circuit correctly.

a = cirq.NamedQubit('a')
b = cirq.NamedQubit('b')
op = CRx(0.25 * np.pi)(a, b)
print(cirq.Circuit.from_ops([op]))


class CRx(cirq.TwoQubitGate):

    def __init__(self, theta):
        self.theta = theta

    def _unitary_(self):
      return np.array([
          [1, 0, 0, 0],
          [0, 1, 0, 0],
          [0, 0, np.cos(self.theta), -1j * np.sin(self.theta)],
          [0, 0, -1j * np.sin(self.theta), np.cos(self.theta)]
      ])
    
    def _circuit_diagram_info_(self, args):
        return '@', 'Rx({}π)'.format(self.theta / np.pi)
    

print('cirq.unitary on the gate yields:')
print(np.around(cirq.unitary(CRx(0.25 * np.pi))))
print()

a = cirq.NamedQubit('a')
b = cirq.NamedQubit('b')
op = CRx(0.25 * np.pi)(a, b)
print('Circuit diagram:')
print(cirq.Circuit.from_ops([op]))

class HXGate(cirq.SingleQubitGate):
    
    def _decompose_(self, qubits):
        return cirq.H(*qubits), cirq.X(*qubits)
    
    def __str__(self):
        return 'HX'
    
HX = HXGate()

a = cirq.NamedQubit('a')
circuit = cirq.Circuit.from_ops([HX(a)])
print(circuit)
print(cirq.Circuit.from_ops(cirq.decompose(circuit)))

print(cirq.Circuit.from_ops(cirq.decompose_once(HX(a))))

def my_decompose(op):
    if isinstance(op, cirq.GateOperation) and isinstance(op.gate, HXGate):
        return cirq.Z(*op.qubits), cirq.H(*op.qubits)

cirq.Circuit.from_ops(cirq.decompose(HX(a), intercepting_decomposer=my_decompose))

def keep_h_and_x(op):
    return isinstance(op, cirq.GateOperation) and op.gate in [cirq.H, cirq.X]

print(cirq.decompose(HX(a), keep=keep_h_and_x))

import sympy as sp

a = cirq.NamedQubit('a')
b = cirq.NamedQubit('b')
simulator = cirq.Simulator()

val = sp.Symbol('s')
pow_x_gate = cirq.X**val
circuit = cirq.Circuit()
circuit.append([pow_x_gate(a), pow_x_gate(b)])
print('Circuit with parameterized gates:')
print(circuit)
print()
for y in range(5):
    result = simulator.simulate(circuit, param_resolver={'s': y / 4.0})
    print('s={}: {}'.format(y, np.around(result.final_state, 2)))

resolvers = [cirq.ParamResolver({'s': y / 8.0}) for y in range(9)]
circuit = cirq.Circuit()
circuit.append([pow_x_gate(a), pow_x_gate(b)])
circuit.append([cirq.measure(a), cirq.measure(b)])
results = simulator.run_sweep(program=circuit,
                              params=resolvers,
                              repetitions=10)
for i, result in enumerate(results):
    print('params: {}\n{}'.format(result.params.param_dict, result))

linspace = cirq.Linspace(start=0, stop=1.0, length=11, key='x')
for p in linspace:
    print(p)

import matplotlib.pyplot as plt
plt.plot([1, 2, 3], [5, 3, 10])

# Insert your code here

circuit = cirq.Circuit.from_ops(cirq.depolarize(0.2)(a), cirq.measure(a))
print(circuit)

for i, krauss in enumerate(cirq.channel(cirq.depolarize(0.2))):
    print('{}th krauss operator is {}'.format(i, krauss))
print()

for i, krauss in enumerate(cirq.channel(cirq.depolarize(0.2))):
    pauli_ex = cirq.expand_matrix_in_orthogonal_basis(krauss, cirq.PAULI_BASIS)
    print('{}th krauss operator is {}'.format(i, pauli_ex))

circuit = cirq.Circuit.from_ops(cirq.depolarize(0.2)(a))
print('Circuit:\n{}\n'.format(circuit))
simulator = cirq.DensityMatrixSimulator()
matrix = simulator.simulate(circuit).final_density_matrix
print('Final density matrix:\n{}'.format(matrix))

circuit = cirq.Circuit.from_ops(cirq.depolarize(0.2)(a), cirq.measure(a))
simulator = cirq.DensityMatrixSimulator()
for _ in range(5):
    print(simulator.simulate(circuit).final_density_matrix)

for p, u in cirq.mixture(cirq.depolarize(0.2)):
    print('prob={}\nunitary\n{}\n'.format(p, u))

d = cirq.depolarize(0.2)
print('does cirq.depolarize(0.2) have _channel_? {}'.format('yes' if getattr(d, '_channel_', None) else 'no'))
print('does cirq.depolarize(0.2) have _mixture_? {}'.format('yes' if getattr(d, '_mixture_', None) else 'no'))

circuit = cirq.Circuit.from_ops(cirq.depolarize(0.5).on(a), cirq.measure(a))
simulator = cirq.Simulator()
result = simulator.run(circuit, repetitions=10)
print(result)

noise = cirq.ConstantQubitNoiseModel(cirq.depolarize(0.2))
circuit = cirq.Circuit.from_ops(cirq.H(a), cirq.CNOT(a, b), cirq.measure(a, b))
print('Circuit with no noise:\n{}\n'.format(circuit))

system_qubits = sorted(circuit.all_qubits())
noisy_circuit = cirq.Circuit()
for moment in circuit:
    noisy_circuit.append(noise.noisy_moment(moment, system_qubits))
print('Circuit with noise:\n{}'.format(noisy_circuit))


noise = cirq.ConstantQubitNoiseModel(cirq.depolarize(0.2))
circuit = cirq.Circuit.from_ops(cirq.H(a), cirq.CNOT(a, b), cirq.measure(a, b))

simulator = cirq.DensityMatrixSimulator(noise=noise)
for i, step in enumerate(simulator.simulate_moment_steps(circuit)):
    print('After step {} state was\n{}\n'.format(i, step.density_matrix()))

print(cirq.google.Bristlecone)

brissy = cirq.google.Bristlecone
op = cirq.X.on(cirq.GridQubit(5, 5))
print(brissy.duration_of(op))

q55 = cirq.GridQubit(5, 5)
q56 = cirq.GridQubit(5, 6)
q66 = cirq.GridQubit(6, 6)
q67 = cirq.GridQubit(6, 7)
ops = [cirq.CZ(q55, q56), cirq.CZ(q66, q67)]
circuit = cirq.Circuit.from_ops(ops)
print(circuit)
print('But when we validate it against the device:')
cirq.google.Bristlecone.validate_circuit(circuit)
# (this should throw an error)

ops = [cirq.CZ(q55, q56), cirq.CZ(q66, q67)]
circuit = cirq.Circuit(device=cirq.google.Bristlecone)
circuit.append(ops)
print(circuit)


# your code here

class XZOptimizer(cirq.PointOptimizer):
    """Replaces an X followed by a Z with a Y."""
    
    def optimization_at(self, circuit, index, op):
        # Is the gate an X gate?
        if isinstance(op, cirq.GateOperation) and (op.gate == cirq.X):
            next_op_index = circuit.next_moment_operating_on(op.qubits, index + 1)
            qubit = op.qubits[0]
            if next_op_index is not None:
                next_op = circuit.operation_at(qubit, next_op_index)
                if isinstance(next_op, cirq.GateOperation) and  (next_op.gate == cirq.Z):
                    new_op = cirq.Y.on(qubit)
                    return cirq.PointOptimizationSummary(
                        clear_span = next_op_index - index + 1,
                        clear_qubits=op.qubits, 
                        new_operations=[new_op])
        
opt = XZOptimizer()
circuit = cirq.Circuit.from_ops(cirq.X(a), cirq.Z(a), cirq.CZ(a, b), cirq.X(a))
print('Before\n{}\n'. format(circuit))
opt.optimize_circuit(circuit)
print('After\n{}'.format(circuit))

# Insert your code here.

# Here is a circuit to test this on.
circuit = cirq.Circuit.from_ops(cirq.H(a), cirq.H(a), cirq.H(b), 
                                cirq.CNOT(a, b), cirq.H(a), cirq.H(b), 
                                cirq.CZ(a, b))
# Instantiate your optimizer

# And check that it worked.
print(my_opt.optimizer_circuit(circuit))

cirq.google.is_native_xmon_op(cirq.X.on(cirq.NamedQubit('a')))

cirq.google.is_native_xmon_op(cirq.CNOT.on(cirq.NamedQubit('a'), cirq.NamedQubit('b')))

converter = cirq.google.ConvertToXmonGates()
converted = converter.convert(cirq.CNOT.on(cirq.NamedQubit('a'), cirq.NamedQubit('b')))
print(cirq.Circuit.from_ops(converted))

circuit = cirq.Circuit.from_ops([cirq.CNOT.on(cirq.NamedQubit('a'), cirq.NamedQubit('b'))])
print(cirq.google.optimized_for_xmon(circuit))


cirq.google.gate_to_proto_dict(cirq.X, [cirq.GridQubit(5, 5)])

result = cirq.experiments.rabi_oscillations(
    sampler=cirq.Simulator(),  # In the future, sampler could point at real hardware.
    qubit=cirq.LineQubit(0)
)
result.plot()

class InconsistentXGate(cirq.SingleQubitGate):

    def _decompose_(self, qubits):
        yield cirq.H(qubits[0])
        yield cirq.Z(qubits[0])
        yield cirq.H(qubits[0])

    def _unitary_(self):
        return np.array([[0, -1j], [1j, 0]])  # Oops! Y instead of X!

cirq.testing.assert_decompose_is_consistent_with_unitary(InconsistentXGate())

a, b, c = cirq.LineQubit.range(3)
circuit = cirq.Circuit.from_ops(cirq.H(a), cirq.H(c), cirq.CNOT(a, b), cirq.CCZ(a, b, c))
print(circuit.to_qasm())

from cirq.contrib.quirk.export_to_quirk import circuit_to_quirk_url
print(circuit_to_quirk_url(circuit))