From db801bbfcaf1f3103e737fe766381fe1b922d769 Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Thu, 29 Apr 2021 22:02:52 -0700 Subject: [PATCH] [XEB] Local wrapper for characterization functionality (#4047) XEB calibration can be run through the calibration api, i.e. by a call to `engine.run_calibration` but since all the code is open-sourced in Cirq and only requires the ability to sample from a quantum processor, we can write a wrapper which can be dispatched to by `cg.workflow.run_calibrations` that submits circuits using a `cirq.Sampler` and performs XEB angle optimization locally. --- cirq-core/cirq/experiments/xeb_fitting.py | 8 +- .../cirq_google/calibration/phased_fsim.py | 75 ++++++++++ .../cirq_google/calibration/workflow.py | 117 ++++++++++++---- .../cirq_google/calibration/workflow_test.py | 92 +++++++++++- .../cirq_google/calibration/xeb_wrapper.py | 110 +++++++++++++++ .../calibration/xeb_wrapper_test.py | 131 ++++++++++++++++++ 6 files changed, 493 insertions(+), 40 deletions(-) create mode 100644 cirq-google/cirq_google/calibration/xeb_wrapper.py create mode 100644 cirq-google/cirq_google/calibration/xeb_wrapper_test.py diff --git a/cirq-core/cirq/experiments/xeb_fitting.py b/cirq-core/cirq/experiments/xeb_fitting.py index ac1d00cb9ea..9a723e03719 100644 --- a/cirq-core/cirq/experiments/xeb_fitting.py +++ b/cirq-core/cirq/experiments/xeb_fitting.py @@ -54,7 +54,7 @@ def benchmark_2q_xeb_fidelities( cycle_depths: Sequence[int], param_resolver: 'cirq.ParamResolverOrSimilarType' = None, pool: Optional['multiprocessing.pool.Pool'] = None, -): +) -> pd.DataFrame: """Simulate and benchmark two-qubit XEB circuits. This uses the estimator from @@ -221,11 +221,7 @@ def get_parameterized_gate(self): @staticmethod def should_parameterize(op: 'cirq.Operation') -> bool: - if isinstance(op.gate, (ops.PhasedFSimGate, ops.FSimGate)): - return True - if op.gate == SQRT_ISWAP: - return True - return False + return isinstance(op.gate, (ops.PhasedFSimGate, ops.ISwapPowGate, ops.FSimGate)) @dataclass(frozen=True) diff --git a/cirq-google/cirq_google/calibration/phased_fsim.py b/cirq-google/cirq_google/calibration/phased_fsim.py index f61f8ec0719..ed7fb82d377 100644 --- a/cirq-google/cirq_google/calibration/phased_fsim.py +++ b/cirq-google/cirq_google/calibration/phased_fsim.py @@ -247,6 +247,7 @@ class PhasedFSimCalibrationResult: quibts a and b either only (a, b) or only (b, a) is present. gate: Characterized gate for each qubit pair. This is copied from the matching PhasedFSimCalibrationRequest and is included to preserve execution context. + options: The options used to gather this result. """ parameters: Dict[Tuple[Qid, Qid], PhasedFSimCharacterization] @@ -467,6 +468,52 @@ def _from_json_dict_(cls, **kwargs): return cls(**kwargs) +@json_serializable_dataclass(frozen=True) +class LocalXEBPhasedFSimCalibrationOptions(XEBPhasedFSimCalibrationOptions): + """Options for configuring a PhasedFSim calibration using a local version of XEB. + + XEB uses the fidelity of random circuits to characterize PhasedFSim gates. The parameters + of the gate are varied by a classical optimizer to maximize the observed fidelities. + + These "Local" options (corresponding to `LocalXEBPhasedFSimCalibrationRequest`) instruct + `cirq_google.run_calibrations` to execute XEB analysis locally (not via the quantum + engine). As such, `run_calibrations` can work with any `cirq.Sampler`, not just + `QuantumEngineSampler`. + + Args: + n_library_circuits: The number of distinct, two-qubit random circuits to use in our + library of random circuits. This should be the same order of magnitude as + `n_combinations`. + n_combinations: We take each library circuit and randomly assign it to qubit pairs. + This parameter controls the number of random combinations of the two-qubit random + circuits we execute. Higher values increase the precision of estimates but linearly + increase experimental runtime. + cycle_depths: We run the random circuits at these cycle depths to fit an exponential + decay in the fidelity. + fatol: The absolute convergence tolerance for the objective function evaluation in + the Nelder-Mead optimization. This controls the runtime of the classical + characterization optimization loop. + xatol: The absolute convergence tolerance for the parameter estimates in + the Nelder-Mead optimization. This controls the runtime of the classical + characterization optimization loop. + fsim_options: An instance of `XEBPhasedFSimCharacterizationOptions` that controls aspects + of the PhasedFSim characterization like initial guesses and which angles to + characterize. + n_processes: The number of multiprocessing processes to analyze the XEB characterization + data. By default, we use a value equal to the number of CPU cores. If `1` is specified, + multiprocessing is not used. + """ + + n_processes: Optional[int] = None + + def create_phased_fsim_request( + self, + pairs: Tuple[Tuple[Qid, Qid], ...], + gate: Gate, + ): + return LocalXEBPhasedFSimCalibrationRequest(pairs=pairs, gate=gate, options=self) + + @json_serializable_dataclass(frozen=True) class FloquetPhasedFSimCalibrationOptions(PhasedFSimCalibrationOptions): """Options specific to Floquet PhasedFSimCalibration. @@ -732,8 +779,36 @@ def _parse_characterized_angles( return dict(records) +@json_serializable_dataclass(frozen=True) +class LocalXEBPhasedFSimCalibrationRequest(PhasedFSimCalibrationRequest): + """PhasedFSim characterization request for local cross entropy benchmarking (XEB) calibration. + + A "Local" request (corresponding to `LocalXEBPhasedFSimCalibrationOptions`) instructs + `cirq_google.run_calibrations` to execute XEB analysis locally (not via the quantum + engine). As such, `run_calibrations` can work with any `cirq.Sampler`, not just + `QuantumEngineSampler`. + + Attributes: + options: local-XEB-specific characterization options. + """ + + options: LocalXEBPhasedFSimCalibrationOptions + + def parse_result(self, result: CalibrationResult) -> PhasedFSimCalibrationResult: + raise NotImplementedError('Not applicable for local calibrations') + + def to_calibration_layer(self) -> CalibrationLayer: + raise NotImplementedError('Not applicable for local calibrations') + + @json_serializable_dataclass(frozen=True) class XEBPhasedFSimCalibrationRequest(PhasedFSimCalibrationRequest): + """PhasedFSim characterization request for cross entropy benchmarking (XEB) calibration. + + Attributes: + options: XEB-specific characterization options. + """ + options: XEBPhasedFSimCalibrationOptions def to_calibration_layer(self) -> CalibrationLayer: diff --git a/cirq-google/cirq_google/calibration/workflow.py b/cirq-google/cirq_google/calibration/workflow.py index 15b8d3d8fba..09042e15a14 100644 --- a/cirq-google/cirq_google/calibration/workflow.py +++ b/cirq-google/cirq_google/calibration/workflow.py @@ -24,6 +24,7 @@ Tuple, Union, cast, + TYPE_CHECKING, ) from cirq.circuits import Circuit @@ -55,10 +56,15 @@ try_convert_sqrt_iswap_to_fsim, PhasedFSimCalibrationOptions, RequestT, + LocalXEBPhasedFSimCalibrationRequest, ) +from cirq_google.calibration.xeb_wrapper import run_local_xeb_calibration from cirq_google.engine import Engine, QuantumEngineSampler from cirq_google.serializable_gate_set import SerializableGateSet +if TYPE_CHECKING: + import cirq + _CALIBRATION_IRRELEVANT_GATES = MeasurementGate, SingleQubitGate, WaitGate @@ -600,6 +606,53 @@ def _merge_into_calibrations( return index +def _run_calibrations_via_engine( + calibration_requests: Sequence[PhasedFSimCalibrationRequest], + engine: Engine, + processor_id: str, + gate_set: SerializableGateSet, + max_layers_per_request: int = 1, + progress_func: Optional[Callable[[int, int], None]] = None, +): + """Helper function for run_calibrations. + + This batches and runs calibration requests the normal way: by using engine.run_calibration. + This function assumes that all inputs have been validated (by `run_calibrations`). + """ + results = [] + nested_calibration_layers = [ + [ + calibration.to_calibration_layer() + for calibration in calibration_requests[offset : offset + max_layers_per_request] + ] + for offset in range(0, len(calibration_requests), max_layers_per_request) + ] + + for cal_layers in nested_calibration_layers: + job = engine.run_calibration(cal_layers, processor_id=processor_id, gate_set=gate_set) + request_results = job.calibration_results() + results += [ + calibration.parse_result(result) + for calibration, result in zip(calibration_requests, request_results) + ] + if progress_func: + progress_func(len(results), len(calibration_requests)) + return results + + +def _run_local_calibrations_via_sampler( + calibration_requests: Sequence[PhasedFSimCalibrationRequest], + sampler: 'cirq.Sampler', +): + """Helper function used by `run_calibrations` to run Local calibrations with a Sampler.""" + return [ + run_local_xeb_calibration( + cast(LocalXEBPhasedFSimCalibrationRequest, calibration_request), sampler + ) + for calibration_request in calibration_requests + ] + + def run_calibrations( calibrations: Sequence[PhasedFSimCalibrationRequest], sampler: Union[Engine, Sampler], @@ -638,6 +691,13 @@ def run_calibrations( if not calibrations: return [] + calibration_request_types = set(type(cr) for cr in calibrations) + if len(calibration_request_types) > 1: + raise ValueError( + f"All calibrations must be of the same type. You gave: {calibration_request_types}" + ) + (calibration_request_type,) = calibration_request_types + if isinstance(sampler, Engine): engine: Optional[Engine] = sampler elif isinstance(sampler, QuantumEngineSampler): @@ -649,36 +709,33 @@ def run_calibrations( if engine is not None: if processor_id is None: - raise ValueError('processor_id must be provided when running on the engine') + raise ValueError('processor_id must be provided.') if gate_set is None: - raise ValueError('gate_set must be provided when running on the engine') - - results = [] + raise ValueError('gate_set must be provided.') + + if calibration_request_type == LocalXEBPhasedFSimCalibrationRequest: + sampler = engine.sampler(processor_id=processor_id, gate_set=gate_set) + return _run_local_calibrations_via_sampler(calibrations, sampler) + + return _run_calibrations_via_engine( + calibrations, + engine, + processor_id, + gate_set, + max_layers_per_request, + progress_func, + ) - requests = [ - [ - calibration.to_calibration_layer() - for calibration in calibrations[offset : offset + max_layers_per_request] - ] - for offset in range(0, len(calibrations), max_layers_per_request) - ] + if calibration_request_type == LocalXEBPhasedFSimCalibrationRequest: + return _run_local_calibrations_via_sampler(calibrations, sampler=cast(Sampler, sampler)) - for request in requests: - job = engine.run_calibration(request, processor_id=processor_id, gate_set=gate_set) - request_results = job.calibration_results() - results += [ - calibration.parse_result(result) - for calibration, result in zip(calibrations, request_results) - ] - if progress_func: - progress_func(len(results), len(calibrations)) - - elif isinstance(sampler, PhasedFSimEngineSimulator): - results = sampler.get_calibrations(calibrations) - else: - raise ValueError(f'Unsupported sampler type {type(sampler)}') + if isinstance(sampler, PhasedFSimEngineSimulator): + return sampler.get_calibrations(calibrations) - return results + raise ValueError( + f'Unsupported sampler/request combination: Sampler {sampler} cannot run ' + f'calibration request of type {calibration_request_type}' + ) def make_zeta_chi_gamma_compensation_for_moments( @@ -1014,10 +1071,10 @@ def run_zeta_chi_gamma_compensation_for_moments( circuit, options, gates_translator, merge_subsets=merge_subsets ) characterizations = run_calibrations( - requests, - sampler, - processor_id, - gate_set, + calibrations=requests, + sampler=sampler, + processor_id=processor_id, + gate_set=gate_set, max_layers_per_request=max_layers_per_request, progress_func=progress_func, ) diff --git a/cirq-google/cirq_google/calibration/workflow_test.py b/cirq-google/cirq_google/calibration/workflow_test.py index e6f9093e7ce..66fd3745fc8 100644 --- a/cirq-google/cirq_google/calibration/workflow_test.py +++ b/cirq-google/cirq_google/calibration/workflow_test.py @@ -11,17 +11,21 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import itertools from typing import Optional - from unittest import mock -import itertools + import numpy as np import pytest import cirq import cirq_google import cirq_google.calibration.workflow as workflow - +import cirq_google.calibration.xeb_wrapper +from cirq.experiments import ( + random_rotations_between_grid_interaction_layers_circuit, + XEBPhasedFSimCharacterizationOptions, +) from cirq_google.calibration.engine_simulator import PhasedFSimEngineSimulator from cirq_google.calibration.phased_fsim import ( ALL_ANGLES_FLOQUET_PHASED_FSIM_CHARACTERIZATION, @@ -32,9 +36,12 @@ PhasedFSimCalibrationResult, WITHOUT_CHI_FLOQUET_PHASED_FSIM_CHARACTERIZATION, ALL_ANGLES_XEB_PHASED_FSIM_CHARACTERIZATION, + LocalXEBPhasedFSimCalibrationRequest, + LocalXEBPhasedFSimCalibrationOptions, + XEBPhasedFSimCalibrationRequest, + XEBPhasedFSimCalibrationOptions, ) - SQRT_ISWAP_INV_PARAMETERS = cirq_google.PhasedFSimCharacterization( theta=np.pi / 4, zeta=0.0, chi=0.0, gamma=0.0, phi=0.0 ) @@ -1340,3 +1347,80 @@ def test_run_zeta_chi_gamma_calibration_for_moments_no_chi() -> None: engine_simulator.final_state_vector(calibrated_circuit.circuit), cirq.final_state_vector(circuit), ) + + +_MOCK_ENGINE_SAMPLER = mock.MagicMock( + spec=cirq_google.QuantumEngineSampler, _processor_ids=['my_fancy_processor'], _gate_set='test' +) + + +@pytest.mark.parametrize('sampler_engine', [cirq.Simulator, _MOCK_ENGINE_SAMPLER]) +def test_run_local(sampler_engine, monkeypatch): + called_times = 0 + + def myfunc( + calibration: LocalXEBPhasedFSimCalibrationRequest, + sampler: 'cirq.Sampler', + ): + nonlocal called_times + assert isinstance(calibration, LocalXEBPhasedFSimCalibrationRequest) + assert sampler is not None + called_times += 1 + return [] + + # Note: you must patch specifically the function imported into `workflow`. + monkeypatch.setattr('cirq_google.calibration.workflow.run_local_xeb_calibration', myfunc) + + qubit_indices = [ + (0, 5), + (0, 6), + (1, 6), + (2, 6), + ] + qubits = [cirq.GridQubit(*idx) for idx in qubit_indices] + + circuits = [ + random_rotations_between_grid_interaction_layers_circuit( + qubits, + depth=depth, + two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP_INV_GATE.on(a, b), + pattern=cirq.experiments.GRID_ALIGNED_PATTERN, + seed=10, + ) + for depth in [5, 10] + ] + + options = LocalXEBPhasedFSimCalibrationOptions( + fsim_options=XEBPhasedFSimCharacterizationOptions( + characterize_zeta=True, + characterize_gamma=True, + characterize_chi=True, + characterize_theta=False, + characterize_phi=False, + theta_default=np.pi / 4, + ), + n_processes=1, + ) + + characterization_requests = [] + for circuit in circuits: + _, characterization_requests = workflow.prepare_characterization_for_moments( + circuit, options=options, initial=characterization_requests + ) + assert len(characterization_requests) == 2 + for cr in characterization_requests: + assert isinstance(cr, LocalXEBPhasedFSimCalibrationRequest) + + workflow.run_calibrations(characterization_requests, sampler_engine) + assert called_times == 2 + + +def test_multiple_calibration_types_error(): + r1 = LocalXEBPhasedFSimCalibrationRequest( + pairs=[], gate=None, options=LocalXEBPhasedFSimCalibrationOptions() + ) + r2 = XEBPhasedFSimCalibrationRequest( + pairs=[], gate=None, options=XEBPhasedFSimCalibrationOptions() + ) + with pytest.raises(ValueError, match=r'must be of the same type\.'): + workflow.run_calibrations([r1, r2], cirq.Simulator()) diff --git a/cirq-google/cirq_google/calibration/xeb_wrapper.py b/cirq-google/cirq_google/calibration/xeb_wrapper.py new file mode 100644 index 00000000000..174e8141394 --- /dev/null +++ b/cirq-google/cirq_google/calibration/xeb_wrapper.py @@ -0,0 +1,110 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import contextlib +import multiprocessing +import multiprocessing.pool +from typing import Optional, Union, Iterator + +import cirq +import cirq.experiments.random_quantum_circuit_generation as rqcg +import cirq.experiments.xeb_fitting as xebf +import cirq.experiments.xeb_sampling as xebsamp +from cirq_google.calibration.phased_fsim import ( + PhasedFSimCalibrationResult, + PhasedFSimCharacterization, + LocalXEBPhasedFSimCalibrationRequest, + LocalXEBPhasedFSimCalibrationOptions, +) + + +@contextlib.contextmanager +def _maybe_multiprocessing_pool( + n_processes: Optional[int] = None, +) -> Iterator[Union['multiprocessing.pool.Pool', None]]: + """Yield a multiprocessing.Pool as a context manager, unless n_processes=1; then yield None, + which should disable multiprocessing in XEB apis.""" + if n_processes == 1: + yield None + return + + with multiprocessing.Pool(processes=n_processes) as pool: + yield pool + + +def run_local_xeb_calibration( + calibration: LocalXEBPhasedFSimCalibrationRequest, + sampler: 'cirq.Sampler', +) -> PhasedFSimCalibrationResult: + """Run a calibration request using `cirq.experiments` XEB utilities and a sampler rather + than `Engine.run_calibrations`. + + Args: + calibration: A LocalXEBPhasedFSimCalibration request describing the XEB characterization + to carry out. + sampler: A sampler to execute circuits. + """ + options: LocalXEBPhasedFSimCalibrationOptions = calibration.options + circuit = cirq.Circuit([calibration.gate.on(*pair) for pair in calibration.pairs]) + + # 2. Set up XEB experiment + cycle_depths = options.cycle_depths + circuits = rqcg.generate_library_of_2q_circuits( + n_library_circuits=options.n_library_circuits, + two_qubit_gate=calibration.gate, + max_cycle_depth=max(cycle_depths), + ) + combs_by_layer = rqcg.get_random_combinations_for_layer_circuit( + n_library_circuits=len(circuits), + n_combinations=options.n_combinations, + layer_circuit=circuit, + ) + + # 3. Sample data + sampled_df = xebsamp.sample_2q_xeb_circuits( + sampler=sampler, + circuits=circuits, + cycle_depths=cycle_depths, + combinations_by_layer=combs_by_layer, + ) + + # 4. Initial fidelities + # initial_fids = xebf.benchmark_2q_xeb_fidelities( + # sampled_df=sampled_df, + # circuits=circuits, + # cycle_depths=cycle_depths, + # ) + + # 5. Characterize by fitting angles. + pcircuits = [xebf.parameterize_circuit(circuit, options.fsim_options) for circuit in circuits] + fatol = options.fatol if options.fatol is not None else 5e-3 + xatol = options.xatol if options.xatol is not None else 5e-3 + with _maybe_multiprocessing_pool(n_processes=options.n_processes) as pool: + char_results = xebf.characterize_phased_fsim_parameters_with_xeb_by_pair( + sampled_df=sampled_df, + parameterized_circuits=pcircuits, + cycle_depths=cycle_depths, + options=options.fsim_options, + pool=pool, + fatol=fatol, + xatol=xatol, + ) + + return PhasedFSimCalibrationResult( + parameters={ + pair: PhasedFSimCharacterization(**param_dict) + for pair, param_dict in char_results.final_params.items() + }, + gate=calibration.gate, + options=options, + ) diff --git a/cirq-google/cirq_google/calibration/xeb_wrapper_test.py b/cirq-google/cirq_google/calibration/xeb_wrapper_test.py new file mode 100644 index 00000000000..8c3b77229a7 --- /dev/null +++ b/cirq-google/cirq_google/calibration/xeb_wrapper_test.py @@ -0,0 +1,131 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import multiprocessing.pool + +import numpy as np +import pandas as pd +import scipy.optimize +import scipy.optimize._minimize + +import cirq +import cirq_google as cg +from cirq.experiments import random_rotations_between_grid_interaction_layers_circuit +from cirq.experiments.xeb_fitting import XEBPhasedFSimCharacterizationOptions +from cirq_google.calibration.phased_fsim import ( + LocalXEBPhasedFSimCalibrationOptions, + LocalXEBPhasedFSimCalibrationRequest, +) +from cirq_google.calibration.xeb_wrapper import ( + run_local_xeb_calibration, + _maybe_multiprocessing_pool, +) + +SQRT_ISWAP = cirq.ISWAP ** -0.5 + + +def _minimize_patch( + fun, + x0, + args=(), + method=None, + jac=None, + hess=None, + hessp=None, + bounds=None, + constraints=(), + tol=None, + callback=None, + options=None, +): + assert method == 'nelder-mead' + + return scipy.optimize.OptimizeResult( + fun=0, + nit=0, + nfev=0, + status=0, + success=True, + message='monkeypatched', + x=x0.copy(), + final_simplex=None, + ) + + +def _benchmark_patch(*args, **kwargs): + return pd.DataFrame() + + +def test_run_calibration(monkeypatch): + monkeypatch.setattr('cirq.experiments.xeb_fitting.scipy.optimize.minimize', _minimize_patch) + monkeypatch.setattr( + 'cirq_google.calibration.xeb_wrapper.xebf.benchmark_2q_xeb_fidelities', _benchmark_patch + ) + qubit_indices = [ + (0, 5), + (0, 6), + (1, 6), + (2, 6), + ] + qubits = [cirq.GridQubit(*idx) for idx in qubit_indices] + sampler = cirq.ZerosSampler() + + circuits = [ + random_rotations_between_grid_interaction_layers_circuit( + qubits, + depth=depth, + two_qubit_op_factory=lambda a, b, _: SQRT_ISWAP.on(a, b), + pattern=cirq.experiments.GRID_ALIGNED_PATTERN, + seed=10, + ) + for depth in [5, 10] + ] + + options = LocalXEBPhasedFSimCalibrationOptions( + fsim_options=XEBPhasedFSimCharacterizationOptions( + characterize_zeta=True, + characterize_gamma=True, + characterize_chi=True, + characterize_theta=False, + characterize_phi=False, + theta_default=np.pi / 4, + ), + n_processes=1, + ) + + characterization_requests = [] + for circuit in circuits: + _, characterization_requests = cg.prepare_characterization_for_moments( + circuit, options=options, initial=characterization_requests + ) + assert len(characterization_requests) == 2 + for cr in characterization_requests: + assert isinstance(cr, LocalXEBPhasedFSimCalibrationRequest) + + characterizations = [ + run_local_xeb_calibration(request, sampler) for request in characterization_requests + ] + + final_params = dict() + for c in characterizations: + final_params.update(c.parameters) + assert len(final_params) == 3 # pairs + + +def test_maybe_pool(): + with _maybe_multiprocessing_pool(1) as pool: + assert pool is None + + with _maybe_multiprocessing_pool(2) as pool: + assert isinstance(pool, multiprocessing.pool.Pool)