quantumlib / Cirq

A Python framework for creating, editing, and invoking Noisy Intermediate Scale Quantum (NISQ) circuits.
Apache License 2.0
4.24k stars 1.01k forks source link

Observables instead of Displays? #1539

Closed Strilanc closed 4 years ago

Strilanc commented 5 years ago

I was talking with @ncrubin and @mpharrigan about displays, and they made some good points against them. Mainly it comes down to: this feature has a ton of engineering complexity, but cover a lot of use cases outside of what researchers actually need to do. We would likely be better served by, instead of a "compute_displays" method where there can be a series of displays strewn throughout the circuit, having an "estimate_observable" method that specifies a single observable we would like to apply to the output of the circuit.

So the proposal is:

@dabacon @kevinsung

mpharrigan commented 5 years ago

to take an observable instead of searching in the circuit for displays

I think this is a really nice consequence of constraining displays into observables

Strilanc commented 5 years ago

@babbush @john6060 @jarrodmcc

babbush commented 5 years ago

I think this is a nice idea. But when you say "don't allow observables to be added into circuits", what do you mean exactly? Do you mean that all observables will be understood to be taken at the end of a circuit? While this is appropriate for most of the early NISQ experiments, eventually we'll want to be able to schedule measurements during a circuit. I realize that fast feedforward is still years away - that's not what I'm talking about. I'm talking about some scheme like (https://arxiv.org/abs/1711.07500) where measurements during the circuit are used to create dissipative maps that extract entropy. Its something I expect we'll want in the next couple years.

babbush commented 5 years ago

Also, I do think that while doing classical simulations it will often be a useful debugging tool to be able to request expectation values at intermediate parts of the circuit, even though that is clearly not practical to do on the chip.

Strilanc commented 5 years ago

eventually we'll want to be able to schedule measurements during a circuit. I realize that fast feedforward is still years away

Displays cannot be used in that way. Later parts of the circuit do not have access to the statistical result that was computed by the display during its associated sampling runs.

it will often be a useful debugging tool to be able to request expectation values at intermediate parts of the circuit

Yes. But that functionality can be emulated relatively easily by a user submitting the first half of a circuit.

Strilanc commented 5 years ago

Here is some rough prototyping code:

async def sample_observable(sampler: Sampler,
                            circuit: cirq.Circuit,
                            observable: Observable[TKey, TVal],
                            desired_concurrent_jobs: int,
                            desired_total_samples: int) -> TVal:
    pool = CompletionOrderedAsyncWorkPool()
    collector = observable.collector_for_circuit(circuit)

    remaining_samples = desired_total_samples
    active_jobs = 0

    async def _get_new_pool_work():
        nonlocal remaining_samples
        nonlocal active_jobs
        job = collector.next_job()
        if job is None:
            return None
        remaining_samples -= job.repetitions
        active_jobs += 1
        return job, await sampler.sample_async(
            job.circuit, repetitions=job.repetitions)

    while remaining_samples > 0:
        while active_jobs < desired_concurrent_jobs:
            work = _get_new_pool_work()
            if work is None:
                break
            pool.include_work(work)
        if active_jobs < desired_concurrent_jobs:
            break

        if active_jobs:
            done_job, done_val = await pool.__anext__()
            collector.on_job_result(done_job, done_val)

    pool.no_more_work()
    async for done_job, done_val in pool:
        collector.on_job_result(done_job, done_val)

    return collector.statistic()

class PauliObservable(Observable[cirq.PauliString]):
    def __init__(self, terms: cirq.LinearDict[cirq.PauliString]):
        self.terms = terms

    def collector_for_circuit(self, circuit: cirq.Circuit):
        return PauliObservableCollector(circuit, self)

class PauliObservableCollector(ObservableCollector[cirq.PauliString]):
    def __init__(self, circuit: cirq.Circuit, observable: PauliObservable):
        self._circuit = circuit
        self._observable = observable
        self._zeros = cirq.LinearDict()
        self._ones = cirq.LinearDict()

    def on_job_result(self, job: CircuitJob, result: cirq.TrialResult):
        parities = result.histogram(
            key='out',
            fold_func=lambda rep: np.sum(rep) & 1)
        self._zeros[job.key] += parities[0]
        self._ones[job.key] += parities[1]

    def next_job(self) -> Optional[CircuitJob[cirq.PauliString]]:
        pauli = random.choice(self._observable.terms.keys())
        yield CircuitJob(
            circuit=with_pauli_string_out(self._circuit, pauli),
            repetitions=1000,
            key=pauli)

    def statistic(self) -> float:
        result = 0
        for term, coef in self._observable.terms.items():
            a = self._zeros[term]
            b = self._ones[term]
            if a + b:
                result += coef * (a - b) / (a + b)
        return result
peterse commented 5 years ago

it will often be a useful debugging tool to be able to request expectation values at intermediate parts of the circuit

Yes. But that functionality can be emulated relatively easily by a user submitting the first half of a circuit.

I don't know if this is the right place to discuss this, but do you have an interface in mind for simulating part of a circuit, resolving expectation values, then picking up simulation where it left off to continue?

This functionality would improve efficiency for measurement of noncommuting observables. e.g. to measure <X0 + Z0> the simulator should run the circuit to do sampling on (and return an output wf), and then just apply a Hadamard to the output wf to do sampling for

dabacon commented 4 years ago

A version of this exists in pauli_sum_collector for samplers. I'm going to close this, because I think we are moving to that pattern, though there is still work to be done there.