class UnitarySimulator(AerBackend): """Unitary circuit simulator. Backend options: The following backend options may be used with in the `backend_options` kwarg diction for `UnitarySimulator.run` or `qiskit.execute` * "initial_unitary" (matrix_like): Sets a custom initial unitary matrix for the simulation instead of identity (Default: None). * "zero_threshold" (double): Sets the threshold for truncating small values to zero in the result data (Default: 1e-10). * "max_parallel_threads" (int): Sets the maximum number of CPU cores used by OpenMP for parallelization. If set to 0 the maximum will be set to the number of CPU cores (Default: 0). * "max_parallel_experiments" (int): Sets the maximum number of qobj experiments that may be executed in parallel up to the max_parallel_threads value. If set to 1 parallel circuit execution will be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads (Default: 1). * "max_memory_mb" (int): Sets the maximum size of memory to store a state vector. If a state vector needs more, an error is thrown. In general, a state vector of n-qubits uses 2^n complex values (16 Bytes). If set to 0, the maximum will be automatically set to half the system memory size (Default: 0). * "statevector_parallel_threshold" (int): Sets the threshold that 2 * "n_qubits" must be greater than to enable OpenMP parallelization for matrix multiplication during execution of an experiment. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. Note that setting this too low can reduce performance (Default: 14). """ MAX_QUBIT_MEMORY = int( log2(sqrt(local_hardware_info()['memory'] * (1024**3) / 16))) DEFAULT_CONFIGURATION = { 'backend_name': 'unitary_simulator', 'backend_version': __version__, 'n_qubits': MAX_QUBIT_MEMORY, 'url': 'https://github.com/Qiskit/qiskit-aer', 'simulator': True, 'local': True, 'conditional': False, 'open_pulse': False, 'memory': False, 'max_shots': 1, 'description': 'A Python simulator for computing the unitary' 'matrix for experiments in qobj files', 'coupling_map': None, 'basis_gates': [ 'u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap', 'multiplexer', 'snapshot', 'unitary' ], 'gates': [{ 'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO' }], # Location where we put external libraries that will be loaded at runtime # by the simulator extension 'library_dir': os.path.dirname(__file__) } def __init__(self, configuration=None, provider=None): super().__init__(unitary_controller_execute, BackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION), provider=provider) def _validate(self, qobj, backend_options, noise_model): """Semantic validations of the qobj which cannot be done via schemas. Some of these may later move to backend schemas. 1. Set shots=1 2. No measurements or reset 3. Check number of qubits will fit in local memory. """ name = self.name() if noise_model is not None: raise AerError("{} does not support noise.".format(name)) n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: raise AerError( 'Number of qubits ({}) is greater than max ({}) for "{}" with {} GB system memory.' .format(n_qubits, max_qubits, name, int(local_hardware_info()['memory']))) if qobj.config.shots != 1: logger.info('"%s" only supports 1 shot. Setting shots=1.', name) qobj.config.shots = 1 for experiment in qobj.experiments: exp_name = experiment.header.name if getattr(experiment.config, 'shots', 1) != 1: logger.info( '"%s" only supports 1 shot. ' 'Setting shots=1 for circuit "%s".', name, exp_name) experiment.config.shots = 1 for operation in experiment.instructions: if operation.name in ['measure', 'reset']: raise AerError( 'Unsupported {} instruction {} in circuit {}'.format( name, operation.name, exp_name))
class QasmSimulator(AerBackend): """ Noisy quantum circuit simulator backend. The `QasmSimulator` supports multiple simulation methods and configurable options for each simulation method. These options are specified in a dictionary which may be passed to the simulator using the ``backend_options`` kwarg for :meth:`QasmSimulator.run` or ``qiskit.execute``. The default behavior chooses a simulation method automatically based on the input circuit and noise model. A custom method can be specified using the ``"method"`` field in ``backend_options`` as illustrated in the following example. Available simulation methods and additional backend options are listed below. **Example** .. code-block:: python backend = QasmSimulator() backend_options = {"method": "statevector"} # Circuit execution job = execute(circuits, backend, backend_options=backend_options) # Qobj execution job = backend.run(qobj, backend_options=backend_options) **Simulation method** Available simulation methods are: * ``"statevector"``: A dense statevector simulation that can sample measurement outcomes from *ideal* circuits with all measurements at end of the circuit. For noisy simulations each shot samples a randomly sampled noisy circuit from the noise model. ``"statevector_cpu"`` is an alias of ``"statevector"``. * ``"statevector_gpu"``: A dense statevector simulation that provides the same functionalities with ``"statevector"``. GPU performs the computation to calculate probability amplitudes as CPU does. If no GPU is available, a runtime error is raised. * ``"density_matrix"``: A dense density matrix simulation that may sample measurement outcomes from *noisy* circuits with all measurements at end of the circuit. It can only simulate half the number of qubits as the statevector method. * ``"density_matrix_gpu"``: A dense density matrix simulation that provides the same functionalities with ``"density_matrix"``. GPU performs the computation to calculate probability amplitudes as CPU does. If no GPU is available, a runtime error is raised. * ``"stabilizer"``: An efficient Clifford stabilizer state simulator that can simulate noisy Clifford circuits if all errors in the noise model are also Clifford errors. * ``"extended_stabilizer"``: An approximate simulated based on a ranked-stabilizer decomposition that decomposes circuits into stabilizer state terms. The number of terms grows with the number of non-Clifford gates. * ``"matrix_product_state"``: A tensor-network statevector simulator that uses a Matrix Product State (MPS) representation for the state. * ``"automatic"``: The default behavior where the method is chosen automatically for each circuit based on the circuit instructions, number of qubits, and noise model. **Backend options** The following backend options may be used with in the ``backend_options`` kwarg for :meth:`QasmSimulator.run` or ``qiskit.execute``: * ``"method"`` (str): Set the simulation method. See backend methods for additional information (Default: "automatic"). * ``"precision"`` (str): Set the floating point precision for certain simulation methods to either "single" or "double" precision (default: "double"). * ``"zero_threshold"`` (double): Sets the threshold for truncating small values to zero in the result data (Default: 1e-10). * ``"validation_threshold"`` (double): Sets the threshold for checking if initial states are valid (Default: 1e-8). * ``"max_parallel_threads"`` (int): Sets the maximum number of CPU cores used by OpenMP for parallelization. If set to 0 the maximum will be set to the number of CPU cores (Default: 0). * ``"max_parallel_experiments"`` (int): Sets the maximum number of qobj experiments that may be executed in parallel up to the max_parallel_threads value. If set to 1 parallel circuit execution will be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads (Default: 1). * ``"max_parallel_shots"`` (int): Sets the maximum number of shots that may be executed in parallel during each experiment execution, up to the max_parallel_threads value. If set to 1 parallel shot execution will be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads. Note that this cannot be enabled at the same time as parallel experiment execution (Default: 0). * ``"max_memory_mb"`` (int): Sets the maximum size of memory to store a state vector. If a state vector needs more, an error is thrown. In general, a state vector of n-qubits uses 2^n complex values (16 Bytes). If set to 0, the maximum will be automatically set to half the system memory size (Default: 0). * ``"optimize_ideal_threshold"`` (int): Sets the qubit threshold for applying circuit optimization passes on ideal circuits. Passes include gate fusion and truncation of unused qubits (Default: 5). * ``"optimize_noise_threshold"`` (int): Sets the qubit threshold for applying circuit optimization passes on ideal circuits. Passes include gate fusion and truncation of unused qubits (Default: 12). These backend options only apply when using the ``"statevector"`` simulation method: * ``"statevector_parallel_threshold"`` (int): Sets the threshold that the number of qubits must be greater than to enable OpenMP parallelization for matrix multiplication during execution of an experiment. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. Note that setting this too low can reduce performance (Default: 14). * ``"statevector_sample_measure_opt"`` (int): Sets the threshold that the number of qubits must be greater than to enable a large qubit optimized implementation of measurement sampling. Note that setting this two low can reduce performance (Default: 10) These backend options only apply when using the ``"stabilizer"`` simulation method: * ``"stabilizer_max_snapshot_probabilities"`` (int): set the maximum qubit number for the `~qiskit.providers.aer.extensions.SnapshotProbabilities` instruction (Default: 32). These backend options only apply when using the ``"extended_stabilizer"`` simulation method: * ``"extended_stabilizer_measure_sampling"`` (bool): Enable measure sampling optimization on supported circuits. This prevents the simulator from re-running the measure monte-carlo step for each shot. Enabling measure sampling may reduce accuracy of the measurement counts if the output distribution is strongly peaked (Default: False). * ``"extended_stabilizer_mixing_time"`` (int): Set how long the monte-carlo method runs before performing measurements. If the output distribution is strongly peaked, this can be decreased alongside setting extended_stabilizer_disable_measurement_opt to True (Default: 5000). * ``"extended_stabilizer_approximation_error"`` (double): Set the error in the approximation for the extended_stabilizer method. A smaller error needs more memory and computational time (Default: 0.05). * ``"extended_stabilizer_norm_estimation_samples"`` (int): Number of samples used to compute the correct normalization for a statevector snapshot (Default: 100). * ``"extended_stabilizer_parallel_threshold"`` (int): Set the minimum size of the extended stabilizer decomposition before we enable OpenMP parallelization. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads (Default: 100). These backend options apply in circuit optimization passes: * ``"fusion_enable"`` (bool): Enable fusion optimization in circuit optimization passes [Default: True] * ``"fusion_verbose"`` (bool): Output gates generated in fusion optimization into metadata [Default: False] * ``"fusion_max_qubit"`` (int): Maximum number of qubits for a operation generated in a fusion optimization [Default: 5] * ``"fusion_threshold"`` (int): Threshold that number of qubits must be greater than or equal to enable fusion optimization [Default: 20] These backend options only apply when using the ``"matrix_product_state"`` simulation method: * ``"matrix_product_state_max_bond_dimension"`` (int): Sets a limit on the number of Schmidt coefficients retained at the end of the svd algorithm. Coefficients beyond this limit will be discarded. (Default: None, i.e., no limit on the bond dimension). * ``"matrix_product_state_truncation_threshold"`` (double): Discard the smallest coefficients for which the sum of their squares is smaller than this threshold. (Default: 1e-16). """ MAX_QUBIT_MEMORY = int( log2(local_hardware_info()['memory'] * (1024**3) / 16)) DEFAULT_CONFIGURATION = { 'backend_name': 'qasm_simulator', 'backend_version': __version__, 'n_qubits': MAX_QUBIT_MEMORY, 'url': 'https://github.com/Qiskit/qiskit-aer', 'simulator': True, 'local': True, 'conditional': True, 'open_pulse': False, 'memory': True, 'max_shots': int(1e6), 'description': 'A C++ simulator with realistic noise for QASM Qobj files', 'coupling_map': None, 'basis_gates': [ 'u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'swap', 'ccx', 'unitary', 'diagonal', 'initialize', 'cu1', 'cu2', 'cu3', 'cswap', 'mcx', 'mcy', 'mcz', 'mcu1', 'mcu2', 'mcu3', 'mcswap', 'multiplexer', 'kraus', 'roerror' ], 'gates': [{ 'name': 'u1', 'parameters': ['lam'], 'conditional': True, 'description': 'Single-qubit gate [[1, 0], [0, exp(1j*lam)]]', 'qasm_def': 'gate u1(lam) q { U(0,0,lam) q; }' }, { 'name': 'u2', 'parameters': ['phi', 'lam'], 'conditional': True, 'description': 'Single-qubit gate [[1, -exp(1j*lam)], [exp(1j*phi), exp(1j*(phi+lam))]]/sqrt(2)', 'qasm_def': 'gate u2(phi,lam) q { U(pi/2,phi,lam) q; }' }, { 'name': 'u3', 'parameters': ['theta', 'phi', 'lam'], 'conditional': True, 'description': 'Single-qubit gate with three rotation angles', 'qasm_def': 'gate u3(theta,phi,lam) q { U(theta,phi,lam) q; }' }, { 'name': 'cx', 'parameters': [], 'conditional': True, 'description': 'Two-qubit Controlled-NOT gate', 'qasm_def': 'gate cx c,t { CX c,t; }' }, { 'name': 'cz', 'parameters': [], 'conditional': True, 'description': 'Two-qubit Controlled-Z gate', 'qasm_def': 'gate cz a,b { h b; cx a,b; h b; }' }, { 'name': 'id', 'parameters': [], 'conditional': True, 'description': 'Single-qubit identity gate', 'qasm_def': 'gate id a { U(0,0,0) a; }' }, { 'name': 'x', 'parameters': [], 'conditional': True, 'description': 'Single-qubit Pauli-X gate', 'qasm_def': 'gate x a { U(pi,0,pi) a; }' }, { 'name': 'y', 'parameters': [], 'conditional': True, 'description': 'Single-qubit Pauli-Y gate', 'qasm_def': 'TODO' }, { 'name': 'z', 'parameters': [], 'conditional': True, 'description': 'Single-qubit Pauli-Z gate', 'qasm_def': 'TODO' }, { 'name': 'h', 'parameters': [], 'conditional': True, 'description': 'Single-qubit Hadamard gate', 'qasm_def': 'TODO' }, { 'name': 's', 'parameters': [], 'conditional': True, 'description': 'Single-qubit phase gate', 'qasm_def': 'TODO' }, { 'name': 'sdg', 'parameters': [], 'conditional': True, 'description': 'Single-qubit adjoint phase gate', 'qasm_def': 'TODO' }, { 'name': 't', 'parameters': [], 'conditional': True, 'description': 'Single-qubit T gate', 'qasm_def': 'TODO' }, { 'name': 'tdg', 'parameters': [], 'conditional': True, 'description': 'Single-qubit adjoint T gate', 'qasm_def': 'TODO' }, { 'name': 'swap', 'parameters': [], 'conditional': True, 'description': 'Two-qubit SWAP gate', 'qasm_def': 'TODO' }, { 'name': 'ccx', 'parameters': [], 'conditional': True, 'description': 'Three-qubit Toffoli gate', 'qasm_def': 'TODO' }, { 'name': 'cswap', 'parameters': [], 'conditional': True, 'description': 'Three-qubit Fredkin (controlled-SWAP) gate', 'qasm_def': 'TODO' }, { 'name': 'unitary', 'parameters': ['matrix'], 'conditional': True, 'description': 'N-qubit unitary gate. ' 'The parameter is the N-qubit matrix to apply.', 'qasm_def': 'unitary(matrix) q1, q2,...' }, { 'name': 'diagonal', 'parameters': ['diag_elements'], 'conditional': True, 'description': 'N-qubit diagonal unitary gate. The parameters are the' ' diagonal entries of the N-qubit matrix to apply.', 'qasm_def': 'TODO' }, { 'name': 'initialize', 'parameters': ['vector'], 'conditional': False, 'description': 'N-qubit state initialize. ' 'Resets qubits then sets statevector to the parameter vector.', 'qasm_def': 'initialize(vector) q1, q2,...' }, { 'name': 'cu1', 'parameters': ['lam'], 'conditional': True, 'description': 'Two-qubit Controlled-u1 gate', 'qasm_def': 'TODO' }, { 'name': 'cu2', 'parameters': ['phi', 'lam'], 'conditional': True, 'description': 'Two-qubit Controlled-u2 gate', 'qasm_def': 'TODO' }, { 'name': 'cu3', 'parameters': ['theta', 'phi', 'lam'], 'conditional': True, 'description': 'Two-qubit Controlled-u3 gate', 'qasm_def': 'TODO' }, { 'name': 'mcx', 'parameters': [], 'conditional': True, 'description': 'N-qubit multi-controlled-X gate', 'qasm_def': 'TODO' }, { 'name': 'mcy', 'parameters': [], 'conditional': True, 'description': 'N-qubit multi-controlled-Y gate', 'qasm_def': 'TODO' }, { 'name': 'mcz', 'parameters': [], 'conditional': True, 'description': 'N-qubit multi-controlled-Z gate', 'qasm_def': 'TODO' }, { 'name': 'mcu1', 'parameters': ['lam'], 'conditional': True, 'description': 'N-qubit multi-controlled-u1 gate', 'qasm_def': 'TODO' }, { 'name': 'mcu2', 'parameters': ['phi', 'lam'], 'conditional': True, 'description': 'N-qubit multi-controlled-u2 gate', 'qasm_def': 'TODO' }, { 'name': 'mcu3', 'parameters': ['theta', 'phi', 'lam'], 'conditional': True, 'description': 'N-qubit multi-controlled-u3 gate', 'qasm_def': 'TODO' }, { 'name': 'mcswap', 'parameters': [], 'conditional': True, 'description': 'N-qubit multi-controlled-SWAP gate', 'qasm_def': 'TODO' }, { 'name': 'multiplexer', 'parameters': ['mat1', 'mat2', '...'], 'conditional': True, 'description': 'N-qubit multi-plexer gate. ' 'The input parameters are the gates for each value.', 'qasm_def': 'TODO' }, { 'name': 'kraus', 'parameters': ['mat1', 'mat2', '...'], 'conditional': True, 'description': 'N-qubit Kraus error instruction. ' 'The input parameters are the Kraus matrices.', 'qasm_def': 'TODO' }, { 'name': 'roerror', 'parameters': ['matrix'], 'conditional': False, 'description': 'N-bit classical readout error instruction. ' 'The input parameter is the readout error probability matrix.', 'qasm_def': 'TODO' }] } def __init__(self, configuration=None, provider=None): super().__init__(qasm_controller_execute, QasmBackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION), provider=provider) def _validate(self, qobj, backend_options, noise_model): """Semantic validations of the qobj which cannot be done via schemas. Warn if no measurements in circuit with classical registers. """ for experiment in qobj.experiments: # If circuit contains classical registers but not # measurements raise a warning if experiment.config.memory_slots > 0: # Check if measure opts missing no_measure = True for op in experiment.instructions: if not no_measure: break # we don't need to check any more ops if no_measure and op.name == "measure": no_measure = False # Print warning if clbits but no measure if no_measure: logger.warning( 'No measurements in circuit "%s": ' 'count data will return all zeros.', experiment.header.name)
class UnitarySimulator(AerBackend): """Ideal quantum circuit unitary simulator. **Backend options** The following backend options may be used with in the ``backend_options`` kwarg for :meth:`UnitarySimulator.run` or ``qiskit.execute``. * ``"initial_unitary"`` (matrix_like): Sets a custom initial unitary matrix for the simulation instead of identity (Default: None). * ``"validation_threshold"`` (double): Sets the threshold for checking if initial unitary and target unitary are unitary matrices. (Default: 1e-8). * ``"zero_threshold"`` (double): Sets the threshold for truncating small values to zero in the result data (Default: 1e-10). * ``"max_parallel_threads"`` (int): Sets the maximum number of CPU cores used by OpenMP for parallelization. If set to 0 the maximum will be set to the number of CPU cores (Default: 0). * ``"max_parallel_experiments"`` (int): Sets the maximum number of qobj experiments that may be executed in parallel up to the max_parallel_threads value. If set to 1 parallel circuit execution will be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads (Default: 1). * ``"max_memory_mb"`` (int): Sets the maximum size of memory to store a state vector. If a state vector needs more, an error is thrown. In general, a state vector of n-qubits uses 2^n complex values (16 Bytes). If set to 0, the maximum will be automatically set to half the system memory size (Default: 0). * ``"statevector_parallel_threshold"`` (int): Sets the threshold that 2 * "n_qubits" must be greater than to enable OpenMP parallelization for matrix multiplication during execution of an experiment. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. Note that setting this too low can reduce performance (Default: 14). """ MAX_QUBIT_MEMORY = int( log2(sqrt(local_hardware_info()['memory'] * (1024**3) / 16))) DEFAULT_CONFIGURATION = { 'backend_name': 'unitary_simulator', 'backend_version': __version__, 'n_qubits': MAX_QUBIT_MEMORY, 'url': 'https://github.com/Qiskit/qiskit-aer', 'simulator': True, 'local': True, 'conditional': False, 'open_pulse': False, 'memory': False, 'max_shots': int(1e6), # Note that this backend will only ever # perform a single shot. This value is just # so that the default shot value for execute # will not raise an error when trying to run # a simulation 'description': 'A C++ unitary simulator for QASM Qobj files', 'coupling_map': None, 'basis_gates': [ 'u1', 'u2', 'u3', 'p', 'r', 'rx', 'ry', 'rz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 'sx', 't', 'tdg', 'swap', 'cx', 'cy', 'cz', 'csx', 'cp', 'cu1', 'cu2', 'cu3', 'rxx', 'ryy', 'rzz', 'rzx', 'ccx', 'cswap', 'mcx', 'mcy', 'mcz', 'mcsx', 'mcp', 'mcu1', 'mcu2', 'mcu3', 'mcrx', 'mcry', 'mcrz', 'mcr', 'mcswap', 'unitary', 'diagonal', 'multiplexer', 'delay' ], 'gates': [] } def __init__(self, configuration=None, provider=None): super().__init__(unitary_controller_execute(), QasmBackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION), provider=provider) def _validate(self, qobj, backend_options, noise_model): """Semantic validations of the qobj which cannot be done via schemas. Some of these may later move to backend schemas. 1. Set shots=1 2. No measurements or reset 3. Check number of qubits will fit in local memory. """ name = self.name() if noise_model is not None: raise AerError("{} does not support noise.".format(name)) n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: raise AerError( 'Number of qubits ({}) is greater than max ({}) for "{}" with {} GB system memory.' .format(n_qubits, max_qubits, name, int(local_hardware_info()['memory']))) if qobj.config.shots != 1: logger.info('"%s" only supports 1 shot. Setting shots=1.', name) qobj.config.shots = 1 for experiment in qobj.experiments: exp_name = experiment.header.name if getattr(experiment.config, 'shots', 1) != 1: logger.info( '"%s" only supports 1 shot. ' 'Setting shots=1 for circuit "%s".', name, exp_name) experiment.config.shots = 1 for operation in experiment.instructions: if operation.name in ['measure', 'reset']: raise AerError( 'Unsupported {} instruction {} in circuit {}'.format( name, operation.name, exp_name))
class QasmSimulatorPy(BaseBackend): """Python implementation of a qasm simulator.""" MAX_QUBITS_MEMORY = int(log2(local_hardware_info()['memory'] * (1024 ** 3) / 16)) DEFAULT_CONFIGURATION = { 'backend_name': 'qasm_simulator', 'backend_version': '2.0.0', 'n_qubits': min(24, MAX_QUBITS_MEMORY), 'url': 'https://github.com/Qiskit/qiskit-terra', 'simulator': True, 'local': True, 'conditional': True, 'open_pulse': False, 'memory': True, 'max_shots': 65536, 'coupling_map': None, 'description': 'A python simulator for qasm experiments', 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id', 'unitary'], 'gates': [ { 'name': 'u1', 'parameters': ['lambda'], 'qasm_def': 'gate u1(lambda) q { U(0,0,lambda) q; }' }, { 'name': 'u2', 'parameters': ['phi', 'lambda'], 'qasm_def': 'gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }' }, { 'name': 'u3', 'parameters': ['theta', 'phi', 'lambda'], 'qasm_def': 'gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }' }, { 'name': 'cx', 'parameters': ['c', 't'], 'qasm_def': 'gate cx c,t { CX c,t; }' }, { 'name': 'id', 'parameters': ['a'], 'qasm_def': 'gate id a { U(0,0,0) a; }' }, { 'name': 'unitary', 'parameters': ['matrix'], 'qasm_def': 'unitary(matrix) q1, q2,...' } ] } DEFAULT_OPTIONS = { "initial_statevector": None, "chop_threshold": 1e-15 } # Class level variable to return the final state at the end of simulation # This should be set to True for the statevector simulator SHOW_FINAL_STATE = False def __init__(self, configuration=None, provider=None): super().__init__(configuration=( configuration or QasmBackendConfiguration.from_dict(self.DEFAULT_CONFIGURATION)), provider=provider) # Define attributes in __init__. self._local_random = np.random.RandomState() self._classical_memory = 0 self._classical_register = 0 self._statevector = 0 self._number_of_cmembits = 0 self._number_of_qubits = 0 self._shots = 0 self._memory = False self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"] self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"] self._qobj_config = None # TEMP self._sample_measure = False def _add_unitary(self, gate, qubits): """Apply an N-qubit unitary matrix. Args: gate (matrix_like): an N-qubit unitary matrix qubits (list): the list of N-qubits. """ # Get the number of qubits num_qubits = len(qubits) # Compute einsum index string for 1-qubit matrix multiplication indexes = einsum_vecmul_index(qubits, self._number_of_qubits) # Convert to complex rank-2N tensor gate_tensor = np.reshape(np.array(gate, dtype=complex), num_qubits * [2, 2]) # Apply matrix multiplication self._statevector = np.einsum(indexes, gate_tensor, self._statevector, dtype=complex, casting='no') def _get_measure_outcome(self, qubit): """Simulate the outcome of measurement of a qubit. Args: qubit (int): the qubit to measure Return: tuple: pair (outcome, probability) where outcome is '0' or '1' and probability is the probability of the returned outcome. """ # Axis for numpy.sum to compute probabilities axis = list(range(self._number_of_qubits)) axis.remove(self._number_of_qubits - 1 - qubit) probabilities = np.sum(np.abs(self._statevector) ** 2, axis=tuple(axis)) # Compute einsum index string for 1-qubit matrix multiplication random_number = self._local_random.rand() if random_number < probabilities[0]: return '0', probabilities[0] # Else outcome was '1' return '1', probabilities[1] def _add_sample_measure(self, measure_params, num_samples): """Generate memory samples from current statevector. Args: measure_params (list): List of (qubit, cmembit) values for measure instructions to sample. num_samples (int): The number of memory samples to generate. Returns: list: A list of memory values in hex format. """ # Get unique qubits that are actually measured and sort in # ascending order measured_qubits = sorted(list({qubit for qubit, cmembit in measure_params})) num_measured = len(measured_qubits) # We use the axis kwarg for numpy.sum to compute probabilities # this sums over all non-measured qubits to return a vector # of measure probabilities for the measured qubits axis = list(range(self._number_of_qubits)) for qubit in reversed(measured_qubits): # Remove from largest qubit to smallest so list position is correct # with respect to position from end of the list axis.remove(self._number_of_qubits - 1 - qubit) probabilities = np.reshape(np.sum(np.abs(self._statevector) ** 2, axis=tuple(axis)), 2 ** num_measured) # Generate samples on measured qubits as ints with qubit # position in the bit-string for each int given by the qubit # position in the sorted measured_qubits list samples = self._local_random.choice(range(2 ** num_measured), num_samples, p=probabilities) # Convert the ints to bitstrings memory = [] for sample in samples: classical_memory = self._classical_memory for qubit, cmembit in measure_params: pos = measured_qubits.index(qubit) qubit_outcome = int((sample & (1 << pos)) >> pos) membit = 1 << cmembit classical_memory = (classical_memory & (~membit)) | (qubit_outcome << cmembit) value = bin(classical_memory)[2:] memory.append(hex(int(value, 2))) return memory def _add_qasm_measure(self, qubit, cmembit, cregbit=None): """Apply a measure instruction to a qubit. Args: qubit (int): qubit is the qubit measured. cmembit (int): is the classical memory bit to store outcome in. cregbit (int, optional): is the classical register bit to store outcome in. """ # get measure outcome outcome, probability = self._get_measure_outcome(qubit) # update classical state membit = 1 << cmembit self._classical_memory = (self._classical_memory & (~membit)) | (int(outcome) << cmembit) if cregbit is not None: regbit = 1 << cregbit self._classical_register = \ (self._classical_register & (~regbit)) | (int(outcome) << cregbit) # update quantum state if outcome == '0': update_diag = [[1 / np.sqrt(probability), 0], [0, 0]] else: update_diag = [[0, 0], [0, 1 / np.sqrt(probability)]] # update classical state self._add_unitary(update_diag, [qubit]) def _add_qasm_reset(self, qubit): """Apply a reset instruction to a qubit. Args: qubit (int): the qubit being rest This is done by doing a simulating a measurement outcome and projecting onto the outcome state while renormalizing. """ # get measure outcome outcome, probability = self._get_measure_outcome(qubit) # update quantum state if outcome == '0': update = [[1 / np.sqrt(probability), 0], [0, 0]] self._add_unitary(update, [qubit]) else: update = [[0, 1 / np.sqrt(probability)], [0, 0]] self._add_unitary(update, [qubit]) def _validate_initial_statevector(self): """Validate an initial statevector""" # If initial statevector isn't set we don't need to validate if self._initial_statevector is None: return # Check statevector is correct length for number of qubits length = len(self._initial_statevector) required_dim = 2 ** self._number_of_qubits if length != required_dim: raise BasicAerError('initial statevector is incorrect length: ' + '{} != {}'.format(length, required_dim)) def _set_options(self, qobj_config=None, backend_options=None): """Set the backend options for all experiments in a qobj""" # Reset default options self._initial_statevector = self.DEFAULT_OPTIONS["initial_statevector"] self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"] if backend_options is None: backend_options = {} # Check for custom initial statevector in backend_options first, # then config second if 'initial_statevector' in backend_options: self._initial_statevector = np.array(backend_options['initial_statevector'], dtype=complex) elif hasattr(qobj_config, 'initial_statevector'): self._initial_statevector = np.array(qobj_config.initial_statevector, dtype=complex) if self._initial_statevector is not None: # Check the initial statevector is normalized norm = np.linalg.norm(self._initial_statevector) if round(norm, 12) != 1: raise BasicAerError('initial statevector is not normalized: ' + 'norm {} != 1'.format(norm)) # Check for custom chop threshold # Replace with custom options if 'chop_threshold' in backend_options: self._chop_threshold = backend_options['chop_threshold'] elif hasattr(qobj_config, 'chop_threshold'): self._chop_threshold = qobj_config.chop_threshold def _initialize_statevector(self): """Set the initial statevector for simulation""" if self._initial_statevector is None: # Set to default state of all qubits in |0> self._statevector = np.zeros(2 ** self._number_of_qubits, dtype=complex) self._statevector[0] = 1 else: self._statevector = self._initial_statevector.copy() # Reshape to rank-N tensor self._statevector = np.reshape(self._statevector, self._number_of_qubits * [2]) def _get_statevector(self): """Return the current statevector""" vec = np.reshape(self._statevector, 2 ** self._number_of_qubits) vec[abs(vec) < self._chop_threshold] = 0.0 return vec def _validate_measure_sampling(self, experiment): """Determine if measure sampling is allowed for an experiment Args: experiment (QobjExperiment): a qobj experiment. """ # If shots=1 we should disable measure sampling. # This is also required for statevector simulator to return the # correct final statevector without silently dropping final measurements. if self._shots <= 1: self._sample_measure = False return # Check for config flag if hasattr(experiment.config, 'allows_measure_sampling'): self._sample_measure = experiment.config.allows_measure_sampling # If flag isn't found do a simple test to see if a circuit contains # no reset instructions, and no gates instructions after # the first measure. else: measure_flag = False for instruction in experiment.instructions: # If circuit contains reset operations we cannot sample if instruction.name == "reset": self._sample_measure = False return # If circuit contains a measure option then we can # sample only if all following operations are measures if measure_flag: # If we find a non-measure instruction # we cannot do measure sampling if instruction.name not in ["measure", "barrier", "id", "u0"]: self._sample_measure = False return elif instruction.name == "measure": measure_flag = True # If we made it to the end of the circuit without returning # measure sampling is allowed self._sample_measure = True def run(self, qobj, backend_options=None): """Run qobj asynchronously. Args: qobj (Qobj): payload of the experiment backend_options (dict): backend options Returns: BasicAerJob: derived from BaseJob Additional Information: backend_options: Is a dict of options for the backend. It may contain * "initial_statevector": vector_like The "initial_statevector" option specifies a custom initial initial statevector for the simulator to be used instead of the all zero state. This size of this vector must be correct for the number of qubits in all experiments in the qobj. Example:: backend_options = { "initial_statevector": np.array([1, 0, 0, 1j]) / np.sqrt(2), } """ self._set_options(qobj_config=qobj.config, backend_options=backend_options) job_id = str(uuid.uuid4()) job = BasicAerJob(self, job_id, self._run_job, qobj) job.submit() return job def _run_job(self, job_id, qobj): """Run experiments in qobj Args: job_id (str): unique id for the job. qobj (Qobj): job description Returns: Result: Result object """ self._validate(qobj) result_list = [] self._shots = qobj.config.shots self._memory = getattr(qobj.config, 'memory', False) self._qobj_config = qobj.config start = time.time() for experiment in qobj.experiments: result_list.append(self.run_experiment(experiment)) end = time.time() result = {'backend_name': self.name(), 'backend_version': self._configuration.backend_version, 'qobj_id': qobj.qobj_id, 'job_id': job_id, 'results': result_list, 'status': 'COMPLETED', 'success': True, 'time_taken': (end - start), 'header': qobj.header.to_dict()} return Result.from_dict(result) def run_experiment(self, experiment): """Run an experiment (circuit) and return a single experiment result. Args: experiment (QobjExperiment): experiment from qobj experiments list Returns: dict: A result dictionary which looks something like:: { "name": name of this experiment (obtained from qobj.experiment header) "seed": random seed used for simulation "shots": number of shots used in the simulation "data": { "counts": {'0x9: 5, ...}, "memory": ['0x9', '0xF', '0x1D', ..., '0x9'] }, "status": status string for the simulation "success": boolean "time_taken": simulation time of this single experiment } Raises: BasicAerError: if an error occurred. """ start = time.time() self._number_of_qubits = experiment.config.n_qubits self._number_of_cmembits = experiment.config.memory_slots self._statevector = 0 self._classical_memory = 0 self._classical_register = 0 self._sample_measure = False # Validate the dimension of initial statevector if set self._validate_initial_statevector() # Get the seed looking in circuit, qobj, and then random. if hasattr(experiment.config, 'seed_simulator'): seed_simulator = experiment.config.seed_simulator elif hasattr(self._qobj_config, 'seed_simulator'): seed_simulator = self._qobj_config.seed_simulator else: # For compatibility on Windows force dyte to be int32 # and set the maximum value to be (2 ** 31) - 1 seed_simulator = np.random.randint(2147483647, dtype='int32') self._local_random.seed(seed=seed_simulator) # Check if measure sampling is supported for current circuit self._validate_measure_sampling(experiment) # List of final counts for all shots memory = [] # Check if we can sample measurements, if so we only perform 1 shot # and sample all outcomes from the final state vector if self._sample_measure: shots = 1 # Store (qubit, cmembit) pairs for all measure ops in circuit to # be sampled measure_sample_ops = [] else: shots = self._shots for _ in range(shots): self._initialize_statevector() # Initialize classical memory to all 0 self._classical_memory = 0 self._classical_register = 0 for operation in experiment.instructions: conditional = getattr(operation, 'conditional', None) if isinstance(conditional, int): conditional_bit_set = (self._classical_register >> conditional) & 1 if not conditional_bit_set: continue elif conditional is not None: mask = int(operation.conditional.mask, 16) if mask > 0: value = self._classical_memory & mask while (mask & 0x1) == 0: mask >>= 1 value >>= 1 if value != int(operation.conditional.val, 16): continue # Check if single gate if operation.name == 'unitary': qubits = operation.qubits gate = operation.params[0] self._add_unitary(gate, qubits) elif operation.name == "h": qubit = operation.qubits[0] gate = h_gate_matrix() self._add_unitary(gate, [qubit]) elif operation.name in ('U', 'u1', 'u2', 'u3'): params = getattr(operation, 'params', None) qubit = operation.qubits[0] gate = single_gate_matrix(operation.name, params) self._add_unitary(gate, [qubit]) # Check if CX gate elif operation.name in ('id', 'u0'): pass elif operation.name in ('CX', 'cx'): qubit0 = operation.qubits[0] qubit1 = operation.qubits[1] gate = cx_gate_matrix() self._add_unitary(gate, [qubit0, qubit1]) # Check if reset elif operation.name == 'reset': qubit = operation.qubits[0] self._add_qasm_reset(qubit) # Check if barrier elif operation.name == 'barrier': pass # Check if measure elif operation.name == 'measure': qubit = operation.qubits[0] cmembit = operation.memory[0] cregbit = operation.register[0] if hasattr(operation, 'register') else None if self._sample_measure: # If sampling measurements record the qubit and cmembit # for this measurement for later sampling measure_sample_ops.append((qubit, cmembit)) else: # If not sampling perform measurement as normal self._add_qasm_measure(qubit, cmembit, cregbit) elif operation.name == 'bfunc': mask = int(operation.mask, 16) relation = operation.relation val = int(operation.val, 16) cregbit = operation.register cmembit = operation.memory if hasattr(operation, 'memory') else None compared = (self._classical_register & mask) - val if relation == '==': outcome = (compared == 0) elif relation == '!=': outcome = (compared != 0) elif relation == '<': outcome = (compared < 0) elif relation == '<=': outcome = (compared <= 0) elif relation == '>': outcome = (compared > 0) elif relation == '>=': outcome = (compared >= 0) else: raise BasicAerError('Invalid boolean function relation.') # Store outcome in register and optionally memory slot regbit = 1 << cregbit self._classical_register = \ (self._classical_register & (~regbit)) | (int(outcome) << cregbit) if cmembit is not None: membit = 1 << cmembit self._classical_memory = \ (self._classical_memory & (~membit)) | (int(outcome) << cmembit) else: backend = self.name() err_msg = '{0} encountered unrecognized operation "{1}"' raise BasicAerError(err_msg.format(backend, operation.name)) # Add final creg data to memory list if self._number_of_cmembits > 0: if self._sample_measure: # If sampling we generate all shot samples from the final statevector memory = self._add_sample_measure(measure_sample_ops, self._shots) else: # Turn classical_memory (int) into bit string and pad zero for unused cmembits outcome = bin(self._classical_memory)[2:] memory.append(hex(int(outcome, 2))) # Add data data = {'counts': dict(Counter(memory))} # Optionally add memory list if self._memory: data['memory'] = memory # Optionally add final statevector if self.SHOW_FINAL_STATE: data['statevector'] = self._get_statevector() # Remove empty counts and memory for statevector simulator if not data['counts']: data.pop('counts') if 'memory' in data and not data['memory']: data.pop('memory') end = time.time() return {'name': experiment.header.name, 'seed_simulator': seed_simulator, 'shots': self._shots, 'data': data, 'status': 'DONE', 'success': True, 'time_taken': (end - start), 'header': experiment.header.to_dict()} def _validate(self, qobj): """Semantic validations of the qobj which cannot be done via schemas.""" n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: raise BasicAerError('Number of qubits {} '.format(n_qubits) + 'is greater than maximum ({}) '.format(max_qubits) + 'for "{}".'.format(self.name())) for experiment in qobj.experiments: name = experiment.header.name if experiment.config.memory_slots == 0: logger.warning('No classical registers in circuit "%s", ' 'counts will be empty.', name) elif 'measure' not in [op.name for op in experiment.instructions]: logger.warning('No measurements in circuit "%s", ' 'classical register will remain all zeros.', name)
class StatevectorSimulatorPy(QasmSimulatorPy): """Python statevector simulator.""" MAX_QUBITS_MEMORY = int( log2(local_hardware_info()['memory'] * (1024**3) / 16)) DEFAULT_CONFIGURATION = { 'backend_name': 'statevector_simulator', 'backend_version': '1.0.0', 'n_qubits': min(24, MAX_QUBITS_MEMORY), 'url': 'https://github.com/Qiskit/qiskit-terra', 'simulator': True, 'local': True, 'conditional': True, 'open_pulse': False, 'memory': True, 'max_shots': 65536, 'coupling_map': None, 'description': 'A Python statevector simulator for qobj files', 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id', 'snapshot'], 'gates': [{ 'name': 'u1', 'parameters': ['lambda'], 'qasm_def': 'gate u1(lambda) q { U(0,0,lambda) q; }' }, { 'name': 'u2', 'parameters': ['phi', 'lambda'], 'qasm_def': 'gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }' }, { 'name': 'u3', 'parameters': ['theta', 'phi', 'lambda'], 'qasm_def': 'gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }' }, { 'name': 'cx', 'parameters': ['c', 't'], 'qasm_def': 'gate cx c,t { CX c,t; }' }, { 'name': 'id', 'parameters': ['a'], 'qasm_def': 'gate id a { U(0,0,0) a; }' }, { 'name': 'snapshot', 'parameters': ['slot'], 'qasm_def': 'gate snapshot(slot) q { TODO }' }] } # Override base class value to return the final state vector SHOW_FINAL_STATE = True def __init__(self, configuration=None, provider=None): super().__init__(configuration=(configuration or BackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION)), provider=provider) def run(self, qobj, backend_options=None): """Run qobj asynchronously. Args: qobj (Qobj): payload of the experiment backend_options (dict): backend options Returns: BasicAerJob: derived from BaseJob Additional Information:: backend_options: Is a dict of options for the backend. It may contain * "initial_statevector": vector_like * "chop_threshold": double The "initial_statevector" option specifies a custom initial initial statevector for the simulator to be used instead of the all zero state. This size of this vector must be correct for the number of qubits in all experiments in the qobj. The "chop_threshold" option specifies a trunctation value for setting small values to zero in the output statevector. The default value is 1e-15. Example:: backend_options = { "initial_statevector": np.array([1, 0, 0, 1j]) / np.sqrt(2), "chop_threshold": 1e-15 } """ return super().run(qobj, backend_options=backend_options) def _validate(self, qobj): """Semantic validations of the qobj which cannot be done via schemas. Some of these may later move to backend schemas. 1. No shots 2. No measurements in the middle """ n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: raise BasicAerError( 'Number of qubits {} '.format(n_qubits) + 'is greater than maximum ({}) '.format(max_qubits) + 'for "{}".'.format(self.name())) if qobj.config.shots != 1: logger.info('"%s" only supports 1 shot. Setting shots=1.', self.name()) qobj.config.shots = 1 for experiment in qobj.experiments: name = experiment.header.name if getattr(experiment.config, 'shots', 1) != 1: logger.info( '"%s" only supports 1 shot. ' 'Setting shots=1 for circuit "%s".', self.name(), name) experiment.config.shots = 1
class QiskitAquaGlobals: """Aqua class for global properties.""" CPU_COUNT = local_hardware_info()['cpus'] def __init__(self) -> None: self._random_seed = None # type: Optional[int] self._num_processes = QiskitAquaGlobals.CPU_COUNT self._random = None self._massive = False @property def random_seed(self) -> Optional[int]: """Return random seed.""" warn_variable('aqua.aqua_globals', 'qiskit.utils.algorithm_globals', 'qiskit-terra', 3) return self._random_seed @random_seed.setter def random_seed(self, seed: Optional[int]) -> None: """Set random seed.""" warn_variable('aqua.aqua_globals', 'qiskit.utils.algorithm_globals', 'qiskit-terra', 3) self._random_seed = seed self._random = None @property def num_processes(self) -> int: """Return num processes.""" warn_variable('aqua.aqua_globals', 'qiskit.utils.algorithm_globals', 'qiskit-terra', 3) return self._num_processes @num_processes.setter def num_processes(self, num_processes: Optional[int]) -> None: """Set num processes. If 'None' is passed, it resets to QiskitAquaGlobals.CPU_COUNT """ warn_variable('aqua.aqua_globals', 'qiskit.utils.algorithm_globals', 'qiskit-terra', 3) if num_processes is None: num_processes = QiskitAquaGlobals.CPU_COUNT elif num_processes < 1: raise AquaError('Invalid Number of Processes {}.'.format(num_processes)) elif num_processes > QiskitAquaGlobals.CPU_COUNT: raise AquaError('Number of Processes {} cannot be greater than cpu count {}.' .format(num_processes, QiskitAquaGlobals.CPU_COUNT)) self._num_processes = num_processes # TODO: change Terra CPU_COUNT until issue # gets resolved: https://github.com/Qiskit/qiskit-terra/issues/1963 try: qiskit.tools.parallel.CPU_COUNT = self.num_processes except Exception as ex: # pylint: disable=broad-except logger.warning("Failed to set qiskit.tools.parallel.CPU_COUNT " "to value: '%s': Error: '%s'", self.num_processes, str(ex)) @property def random(self) -> np.random.Generator: """Return a numpy np.random.Generator (default_rng).""" warn_variable('aqua.aqua_globals', 'qiskit.utils.algorithm_globals', 'qiskit-terra', 3) if self._random is None: self._random = np.random.default_rng(self._random_seed) # type: ignore return self._random @property def massive(self) -> bool: """Return massive to allow processing of large matrices or vectors.""" warn_variable('aqua.aqua_globals', 'qiskit.utils.algorithm_globals', 'qiskit-terra', 3) return self._massive @massive.setter def massive(self, massive: bool) -> None: """Set massive to allow processing of large matrices or vectors.""" warn_variable('aqua.aqua_globals', 'qiskit.utils.algorithm_globals', 'qiskit-terra', 3) self._massive = massive
def test_local_hardware_none_cpu_count(self, cpu_count_mock, vmem_mock, platform_mock): """Test cpu count fallback to 1 when true value can't be determined""" # pylint: disable=unused-argument result = util.local_hardware_info() self.assertEqual(1, result['cpus'])
class StatevectorSimulator(AerBackend): """Aer statevector simulator Backend options: The following backend options may be used with in the `backend_options` kwarg diction for `StatevectorSimulator.run` or `qiskit.execute` * "initial_statevector" (vector_like): Sets a custom initial statevector for the simulation instead of the all zero initial state (Default: None). * "chop_threshold" (double): Sets the threshold for truncating small values to zero in the Result data (Default: 1e-15) * "max_parallel_threads" (int): Sets the maximum number of CPU cores used by OpenMP for parallelization. If set to 0 the maximum will be set to the number of CPU cores (Default: 0). * "max_parallel_experiments" (int): Sets the maximum number of qobj experiments that may be executed in parallel up to the max_parallel_threads value. If set to 1 parallel circuit execution will be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads (Default: 1). * "statevector_parallel_threshold" (int): Sets the threshold that "n_qubits" must be greater than to enable OpenMP parallelization for matrix multiplication during execution of an experiment. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. Note that setting this too low can reduce performance (Default: 12). * "statevector_hpc_gate_opt" (bool): If set to True this enables a different optimzied gate application routine that can increase performance on systems with a large number of CPU cores. For systems with a small number of cores it enabling can reduce performance (Default: False). """ MAX_QUBIT_MEMORY = int( log2(local_hardware_info()['memory'] * (1024**3) / 16)) DEFAULT_CONFIGURATION = { 'backend_name': 'statevector_simulator', 'backend_version': __version__, 'n_qubits': MAX_QUBIT_MEMORY, 'url': 'https://github.com/Qiskit/qiskit-aer', 'simulator': True, 'local': True, 'conditional': True, 'open_pulse': False, 'memory': True, 'max_shots': 1, 'description': 'A C++ statevector simulator for qobj files', 'coupling_map': None, 'basis_gates': [ 'u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap', 'snapshot', 'unitary' ], 'gates': [{ 'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO' }], # Location where we put external libraries that will be loaded at runtime # by the simulator extension 'library_dir': os.path.dirname(__file__) } def __init__(self, configuration=None, provider=None): super().__init__(statevector_controller_execute, BackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION), provider=provider) def _validate(self, qobj, backend_options, noise_model): """Semantic validations of the qobj which cannot be done via schemas. Some of these may later move to backend schemas. 1. Set shots=1. 2. Check number of qubits will fit in local memory. """ name = self.name() if noise_model is not None: logger.error("{} cannot be run with a noise.".format(name)) raise AerError("{} does not support noise.".format(name)) n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: raise AerError('Number of qubits ({}) '.format(n_qubits) + 'is greater than maximum ({}) '.format(max_qubits) + 'for "{}" '.format(name) + 'with {} GB system memory.'.format( int(local_hardware_info()['memory']))) if qobj.config.shots != 1: logger.info('"%s" only supports 1 shot. Setting shots=1.', name) qobj.config.shots = 1 for experiment in qobj.experiments: exp_name = experiment.header.name if getattr(experiment.config, 'shots', 1) != 1: logger.info( '"%s" only supports 1 shot. ' 'Setting shots=1 for circuit "%s".', name, exp_name) experiment.config.shots = 1
class UnitarySimulator(AerBackend): """Ideal quantum circuit unitary simulator. **Backend options** The following backend options may be used with in the ``backend_options`` kwarg for :meth:`UnitarySimulator.run` or ``qiskit.execute``. * ``"initial_unitary"`` (matrix_like): Sets a custom initial unitary matrix for the simulation instead of identity (Default: None). * ``"validation_threshold"`` (double): Sets the threshold for checking if initial unitary and target unitary are unitary matrices. (Default: 1e-8). * ``"zero_threshold"`` (double): Sets the threshold for truncating small values to zero in the result data (Default: 1e-10). * ``"max_parallel_threads"`` (int): Sets the maximum number of CPU cores used by OpenMP for parallelization. If set to 0 the maximum will be set to the number of CPU cores (Default: 0). * ``"max_parallel_experiments"`` (int): Sets the maximum number of qobj experiments that may be executed in parallel up to the max_parallel_threads value. If set to 1 parallel circuit execution will be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads (Default: 1). * ``"max_memory_mb"`` (int): Sets the maximum size of memory to store a state vector. If a state vector needs more, an error is thrown. In general, a state vector of n-qubits uses 2^n complex values (16 Bytes). If set to 0, the maximum will be automatically set to half the system memory size (Default: 0). * ``"statevector_parallel_threshold"`` (int): Sets the threshold that 2 * "n_qubits" must be greater than to enable OpenMP parallelization for matrix multiplication during execution of an experiment. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. Note that setting this too low can reduce performance (Default: 14). """ MAX_QUBIT_MEMORY = int( log2(sqrt(local_hardware_info()['memory'] * (1024**3) / 16))) DEFAULT_CONFIGURATION = { 'backend_name': 'unitary_simulator', 'backend_version': __version__, 'n_qubits': MAX_QUBIT_MEMORY, 'url': 'https://github.com/Qiskit/qiskit-aer', 'simulator': True, 'local': True, 'conditional': False, 'open_pulse': False, 'memory': False, 'max_shots': int(1e6), # Note that this backend will only ever # perform a single shot. This value is just # so that the default shot value for execute # will not raise an error when trying to run # a simulation 'description': 'A C++ unitary simulator for QASM Qobj files', 'coupling_map': None, 'basis_gates': [ 'u1', 'u2', 'u3', 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'swap', 'ccx', 'unitary', 'cu1', 'cu2', 'cu3', 'cswap', 'mcx', 'mcy', 'mcz', 'mcu1', 'mcu2', 'mcu3', 'mcswap', 'multiplexer', ], 'gates': [{ 'name': 'u1', 'parameters': ['lam'], 'conditional': True, 'description': 'Single-qubit gate [[1, 0], [0, exp(1j*lam)]]', 'qasm_def': 'gate u1(lam) q { U(0,0,lam) q; }' }, { 'name': 'u2', 'parameters': ['phi', 'lam'], 'conditional': True, 'description': 'Single-qubit gate [[1, -exp(1j*lam)], [exp(1j*phi), exp(1j*(phi+lam))]]/sqrt(2)', 'qasm_def': 'gate u2(phi,lam) q { U(pi/2,phi,lam) q; }' }, { 'name': 'u3', 'parameters': ['theta', 'phi', 'lam'], 'conditional': True, 'description': 'Single-qubit gate with three rotation angles', 'qasm_def': 'gate u3(theta,phi,lam) q { U(theta,phi,lam) q; }' }, { 'name': 'cx', 'parameters': [], 'conditional': True, 'description': 'Two-qubit Controlled-NOT gate', 'qasm_def': 'gate cx c,t { CX c,t; }' }, { 'name': 'cz', 'parameters': [], 'conditional': True, 'description': 'Two-qubit Controlled-Z gate', 'qasm_def': 'gate cz a,b { h b; cx a,b; h b; }' }, { 'name': 'id', 'parameters': [], 'conditional': True, 'description': 'Single-qubit identity gate', 'qasm_def': 'gate id a { U(0,0,0) a; }' }, { 'name': 'x', 'parameters': [], 'conditional': True, 'description': 'Single-qubit Pauli-X gate', 'qasm_def': 'gate x a { U(pi,0,pi) a; }' }, { 'name': 'y', 'parameters': [], 'conditional': True, 'description': 'Single-qubit Pauli-Y gate', 'qasm_def': 'TODO' }, { 'name': 'z', 'parameters': [], 'conditional': True, 'description': 'Single-qubit Pauli-Z gate', 'qasm_def': 'TODO' }, { 'name': 'h', 'parameters': [], 'conditional': True, 'description': 'Single-qubit Hadamard gate', 'qasm_def': 'TODO' }, { 'name': 's', 'parameters': [], 'conditional': True, 'description': 'Single-qubit phase gate', 'qasm_def': 'TODO' }, { 'name': 'sdg', 'parameters': [], 'conditional': True, 'description': 'Single-qubit adjoint phase gate', 'qasm_def': 'TODO' }, { 'name': 't', 'parameters': [], 'conditional': True, 'description': 'Single-qubit T gate', 'qasm_def': 'TODO' }, { 'name': 'tdg', 'parameters': [], 'conditional': True, 'description': 'Single-qubit adjoint T gate', 'qasm_def': 'TODO' }, { 'name': 'swap', 'parameters': [], 'conditional': True, 'description': 'Two-qubit SWAP gate', 'qasm_def': 'TODO' }, { 'name': 'ccx', 'parameters': [], 'conditional': True, 'description': 'Three-qubit Toffoli gate', 'qasm_def': 'TODO' }, { 'name': 'cswap', 'parameters': [], 'conditional': True, 'description': 'Three-qubit Fredkin (controlled-SWAP) gate', 'qasm_def': 'TODO' }, { 'name': 'unitary', 'parameters': ['matrix'], 'conditional': True, 'description': 'N-qubit arbitrary unitary gate. ' 'The parameter is the N-qubit matrix to apply.', 'qasm_def': 'unitary(matrix) q1, q2,...' }, { 'name': 'cu1', 'parameters': ['lam'], 'conditional': True, 'description': 'Two-qubit Controlled-u1 gate', 'qasm_def': 'TODO' }, { 'name': 'cu2', 'parameters': ['phi', 'lam'], 'conditional': True, 'description': 'Two-qubit Controlled-u2 gate', 'qasm_def': 'TODO' }, { 'name': 'cu3', 'parameters': ['theta', 'phi', 'lam'], 'conditional': True, 'description': 'Two-qubit Controlled-u3 gate', 'qasm_def': 'TODO' }, { 'name': 'mcx', 'parameters': [], 'conditional': True, 'description': 'N-qubit multi-controlled-X gate', 'qasm_def': 'TODO' }, { 'name': 'mcy', 'parameters': [], 'conditional': True, 'description': 'N-qubit multi-controlled-Y gate', 'qasm_def': 'TODO' }, { 'name': 'mcz', 'parameters': [], 'conditional': True, 'description': 'N-qubit multi-controlled-Z gate', 'qasm_def': 'TODO' }, { 'name': 'mcu1', 'parameters': ['lam'], 'conditional': True, 'description': 'N-qubit multi-controlled-u1 gate', 'qasm_def': 'TODO' }, { 'name': 'mcu2', 'parameters': ['phi', 'lam'], 'conditional': True, 'description': 'N-qubit multi-controlled-u2 gate', 'qasm_def': 'TODO' }, { 'name': 'mcu3', 'parameters': ['theta', 'phi', 'lam'], 'conditional': True, 'description': 'N-qubit multi-controlled-u3 gate', 'qasm_def': 'TODO' }, { 'name': 'mcswap', 'parameters': [], 'conditional': True, 'description': 'N-qubit multi-controlled-SWAP gate', 'qasm_def': 'TODO' }, { 'name': 'multiplexer', 'parameters': ['mat1', 'mat2', '...'], 'conditional': True, 'description': 'N-qubit multi-plexer gate. ' 'The input parameters are the gates for each value.', 'qasm_def': 'TODO' }] } def __init__(self, configuration=None, provider=None): super().__init__(unitary_controller_execute, QasmBackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION), provider=provider) def _validate(self, qobj, backend_options, noise_model): """Semantic validations of the qobj which cannot be done via schemas. Some of these may later move to backend schemas. 1. Set shots=1 2. No measurements or reset 3. Check number of qubits will fit in local memory. """ name = self.name() if noise_model is not None: raise AerError("{} does not support noise.".format(name)) n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: raise AerError( 'Number of qubits ({}) is greater than max ({}) for "{}" with {} GB system memory.' .format(n_qubits, max_qubits, name, int(local_hardware_info()['memory']))) if qobj.config.shots != 1: logger.info('"%s" only supports 1 shot. Setting shots=1.', name) qobj.config.shots = 1 for experiment in qobj.experiments: exp_name = experiment.header.name if getattr(experiment.config, 'shots', 1) != 1: logger.info( '"%s" only supports 1 shot. ' 'Setting shots=1 for circuit "%s".', name, exp_name) experiment.config.shots = 1 for operation in experiment.instructions: if operation.name in ['measure', 'reset']: raise AerError( 'Unsupported {} instruction {} in circuit {}'.format( name, operation.name, exp_name))
from the multiprocessing library. """ import os import platform from concurrent.futures import ProcessPoolExecutor from qiskit.exceptions import QiskitError from qiskit.util import local_hardware_info from qiskit.tools.events.pubsub import Publisher # Set parallel flag if os.getenv('QISKIT_IN_PARALLEL') is None: os.environ['QISKIT_IN_PARALLEL'] = 'FALSE' # Number of local physical cpus CPU_COUNT = local_hardware_info()['cpus'] def _task_wrapper(param): (task, value, task_args, task_kwargs) = param return task(value, *task_args, **task_kwargs) def parallel_map( # pylint: disable=dangerous-default-value task, values, task_args=tuple(), task_kwargs={}, num_processes=CPU_COUNT): """ Parallel execution of a mapping of `values` to the function `task`. This
class UnitarySimulatorPy(BaseBackend): """Python implementation of a unitary simulator.""" MAX_QUBITS_MEMORY = int( log2(sqrt(local_hardware_info()['memory'] * (1024**3) / 16))) DEFAULT_CONFIGURATION = { 'backend_name': 'unitary_simulator', 'backend_version': '1.0.0', 'n_qubits': min(24, MAX_QUBITS_MEMORY), 'url': 'https://github.com/Qiskit/qiskit-terra', 'simulator': True, 'local': True, 'conditional': False, 'open_pulse': False, 'memory': False, 'max_shots': 65536, 'coupling_map': None, 'description': 'A python simulator for unitary matrix corresponding to a circuit', 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id'], 'gates': [{ 'name': 'u1', 'parameters': ['lambda'], 'qasm_def': 'gate u1(lambda) q { U(0,0,lambda) q; }' }, { 'name': 'u2', 'parameters': ['phi', 'lambda'], 'qasm_def': 'gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }' }, { 'name': 'u3', 'parameters': ['theta', 'phi', 'lambda'], 'qasm_def': 'gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }' }, { 'name': 'cx', 'parameters': ['c', 't'], 'qasm_def': 'gate cx c,t { CX c,t; }' }, { 'name': 'id', 'parameters': ['a'], 'qasm_def': 'gate id a { U(0,0,0) a; }' }] } DEFAULT_OPTIONS = {"initial_unitary": None, "chop_threshold": 1e-15} def __init__(self, configuration=None, provider=None): super().__init__(configuration=(configuration or BackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION)), provider=provider) # Define attributes inside __init__. self._unitary = None self._number_of_qubits = 0 self._initial_unitary = None self._chop_threshold = 1e-15 def _add_unitary_single(self, gate, qubit): """Apply an arbitrary 1-qubit unitary matrix. Args: gate (matrix_like): a single qubit gate matrix qubit (int): the qubit to apply gate to """ # Convert to complex rank-2 tensor gate_tensor = np.array(gate, dtype=complex) # Compute einsum index string for 1-qubit matrix multiplication indexes = einsum_matmul_index([qubit], self._number_of_qubits) # Apply matrix multiplication self._unitary = np.einsum(indexes, gate_tensor, self._unitary, dtype=complex, casting='no') def _add_unitary_two(self, gate, qubit0, qubit1): """Apply a two-qubit unitary matrix. Args: gate (matrix_like): a the two-qubit gate matrix qubit0 (int): gate qubit-0 qubit1 (int): gate qubit-1 """ # Convert to complex rank-4 tensor gate_tensor = np.reshape(np.array(gate, dtype=complex), 4 * [2]) # Compute einsum index string for 2-qubit matrix multiplication indexes = einsum_matmul_index([qubit0, qubit1], self._number_of_qubits) # Apply matrix multiplication self._unitary = np.einsum(indexes, gate_tensor, self._unitary, dtype=complex, casting='no') def _validate_initial_unitary(self): """Validate an initial unitary matrix""" # If initial unitary isn't set we don't need to validate if self._initial_unitary is None: return # Check unitary is correct length for number of qubits shape = np.shape(self._initial_unitary) required_shape = (2**self._number_of_qubits, 2**self._number_of_qubits) if shape != required_shape: raise BasicAerError('initial unitary is incorrect shape: ' + '{} != 2 ** {}'.format(shape, required_shape)) def _set_options(self, qobj_config=None, backend_options=None): """Set the backend options for all experiments in a qobj""" # Reset default options self._initial_unitary = self.DEFAULT_OPTIONS["initial_unitary"] self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"] if backend_options is None: backend_options = {} # Check for custom initial statevector in backend_options first, # then config second if 'initial_unitary' in backend_options: self._initial_unitary = np.array( backend_options['initial_unitary'], dtype=complex) elif hasattr(qobj_config, 'initial_unitary'): self._initial_unitary = np.array(qobj_config.initial_unitary, dtype=complex) if self._initial_unitary is not None: # Check the initial unitary is actually unitary shape = np.shape(self._initial_unitary) if len(shape) != 2 or shape[0] != shape[1]: raise BasicAerError("initial unitary is not a square matrix") iden = np.eye(len(self._initial_unitary)) u_dagger_u = np.dot(self._initial_unitary.T.conj(), self._initial_unitary) norm = np.linalg.norm(u_dagger_u - iden) if round(norm, 10) != 0: raise BasicAerError("initial unitary is not unitary") # Check the initial statevector is normalized # Check for custom chop threshold # Replace with custom options if 'chop_threshold' in backend_options: self._chop_threshold = backend_options['chop_threshold'] elif hasattr(qobj_config, 'chop_threshold'): self._chop_threshold = qobj_config.chop_threshold def _initialize_unitary(self): """Set the initial unitary for simulation""" self._validate_initial_unitary() if self._initial_unitary is None: # Set to identity matrix self._unitary = np.eye(2**self._number_of_qubits, dtype=complex) else: self._unitary = self._initial_unitary.copy() # Reshape to rank-N tensor self._unitary = np.reshape(self._unitary, self._number_of_qubits * [2, 2]) def _get_unitary(self): """Return the current unitary in JSON Result spec format""" unitary = np.reshape(self._unitary, 2 * [2**self._number_of_qubits]) # Expand complex numbers unitary = np.stack((unitary.real, unitary.imag), axis=-1) # Truncate small values unitary[abs(unitary) < self._chop_threshold] = 0.0 return unitary def run(self, qobj, backend_options=None): """Run qobj asynchronously. Args: qobj (Qobj): payload of the experiment backend_options (dict): backend options Returns: BasicAerJob: derived from BaseJob Additional Information:: backend_options: Is a dict of options for the backend. It may contain * "initial_unitary": matrix_like * "chop_threshold": double The "initial_unitary" option specifies a custom initial unitary matrix for the simulator to be used instead of the identity matrix. This size of this matrix must be correct for the number of qubits inall experiments in the qobj. The "chop_threshold" option specifies a trunctation value for setting small values to zero in the output unitary. The default value is 1e-15. Example:: backend_options = { "initial_unitary": np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]]) "chop_threshold": 1e-15 } """ self._set_options(qobj_config=qobj.config, backend_options=backend_options) job_id = str(uuid.uuid4()) job = BasicAerJob(self, job_id, self._run_job, qobj) job.submit() return job def _run_job(self, job_id, qobj): """Run experiments in qobj. Args: job_id (str): unique id for the job. qobj (Qobj): job description Returns: Result: Result object """ self._validate(qobj) result_list = [] start = time.time() for experiment in qobj.experiments: result_list.append(self.run_experiment(experiment)) end = time.time() result = { 'backend_name': self.name(), 'backend_version': self._configuration.backend_version, 'qobj_id': qobj.qobj_id, 'job_id': job_id, 'results': result_list, 'status': 'COMPLETED', 'success': True, 'time_taken': (end - start), 'header': qobj.header.as_dict() } return Result.from_dict(result) def run_experiment(self, experiment): """Run an experiment (circuit) and return a single experiment result. Args: experiment (QobjExperiment): experiment from qobj experiments list Returns: dict: A result dictionary which looks something like:: { "name": name of this experiment (obtained from qobj.experiment header) "seed": random seed used for simulation "shots": number of shots used in the simulation "data": { "unitary": [[[0.0, 0.0], [1.0, 0.0]], [[1.0, 0.0], [0.0, 0.0]]] }, "status": status string for the simulation "success": boolean "time taken": simulation time of this single experiment } Raises: BasicAerError: if the number of qubits in the circuit is greater than 24. Note that the practical qubit limit is much lower than 24. """ start = time.time() self._number_of_qubits = experiment.header.n_qubits # Validate the dimension of initial unitary if set self._validate_initial_unitary() self._initialize_unitary() for operation in experiment.instructions: # Check if single gate if operation.name in ('U', 'u1', 'u2', 'u3'): params = getattr(operation, 'params', None) qubit = operation.qubits[0] gate = single_gate_matrix(operation.name, params) self._add_unitary_single(gate, qubit) elif operation.name in ('id', 'u0'): pass # Check if CX gate elif operation.name in ('CX', 'cx'): qubit0 = operation.qubits[0] qubit1 = operation.qubits[1] gate = cx_gate_matrix() self._add_unitary_two(gate, qubit0, qubit1) # Check if barrier elif operation.name == 'barrier': pass else: backend = self.name() err_msg = '{0} encountered unrecognized operation "{1}"' raise BasicAerError(err_msg.format(backend, operation.name)) # Add final state to data data = {'unitary': self._get_unitary()} end = time.time() return { 'name': experiment.header.name, 'shots': 1, 'data': data, 'status': 'DONE', 'success': True, 'time_taken': (end - start), 'header': experiment.header.as_dict() } def _validate(self, qobj): """Semantic validations of the qobj which cannot be done via schemas. Some of these may later move to backend schemas. 1. No shots 2. No measurements in the middle """ n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: raise BasicAerError( 'Number of qubits {} '.format(n_qubits) + 'is greater than maximum ({}) '.format(max_qubits) + 'for "{}".'.format(self.name())) if qobj.config.shots != 1: logger.info('"%s" only supports 1 shot. Setting shots=1.', self.name()) qobj.config.shots = 1 for experiment in qobj.experiments: name = experiment.header.name if getattr(experiment.config, 'shots', 1) != 1: logger.info( '"%s" only supports 1 shot. ' 'Setting shots=1 for circuit "%s".', self.name(), name) experiment.config.shots = 1 for operation in experiment.instructions: if operation.name in ['measure', 'reset']: raise BasicAerError( 'Unsupported "%s" instruction "%s" ' + 'in circuit "%s" ', self.name(), operation.name, name)
# copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. # pylint: disable=invalid-name """ Qiskit Aer simulator backend utils """ import os from math import log2 from qiskit.util import local_hardware_info from qiskit.circuit import QuantumCircuit from qiskit.compiler import assemble from qiskit.qobj import QasmQobjInstruction # Available system memory SYSTEM_MEMORY_GB = local_hardware_info()['memory'] # Max number of qubits for complex double statevector # given available system memory MAX_QUBITS_STATEVECTOR = int(log2(SYSTEM_MEMORY_GB * (1024**3) / 16)) # Location where we put external libraries that will be # loaded at runtime by the simulator extension LIBRARY_DIR = os.path.dirname(__file__) LEGACY_METHOD_MAP = { "statevector_cpu": ("statevector", "CPU"), "statevector_gpu": ("statevector", "GPU"), "statevector_thrust": ("statevector", "Thrust"), "density_matrix_cpu": ("density_matrix", "CPU"), "density_matrix_gpu": ("density_matrix", "GPU"),
class QasmSimulator(AerBackend): """Aer quantum circuit simulator Backend options: The following backend options may be used with in the `backend_options` kwarg diction for `QasmSimulator.run` or `qiskit.execute` Simulation method option ------------------------ * "method" (str): Set the simulation method. Allowed values are: * "statevector": Uses a dense statevector simulation. * "stabilizer": uses a Clifford stabilizer state simulator that is only valid for Clifford circuits and noise models. * "extended_stabilizer": Uses an approximate simulator that decomposes circuits into stabilizer state terms, the number of which grows with the number of non-Clifford gates. * "automatic": automatically run on stabilizer simulator if the circuit and noise model supports it. If there is enough available memory, uses the statevector method. Otherwise, uses the extended_stabilizer method (Default: "automatic"). General options --------------- * "zero_threshold" (double): Sets the threshold for truncating small values to zero in the result data (Default: 1e-10). * "validation_threshold" (double): Sets the threshold for checking if initial states are valid (Default: 1e-8). * "max_parallel_threads" (int): Sets the maximum number of CPU cores used by OpenMP for parallelization. If set to 0 the maximum will be set to the number of CPU cores (Default: 0). * "max_parallel_experiments" (int): Sets the maximum number of qobj experiments that may be executed in parallel up to the max_parallel_threads value. If set to 1 parallel circuit execution will be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads (Default: 1). * "max_parallel_shots" (int): Sets the maximum number of shots that may be executed in parallel during each experiment execution, up to the max_parallel_threads value. If set to 1 parallel shot execution wil be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads. Note that this cannot be enabled at the same time as parallel experiment execution (Default: 1). * "max_memory_mb" (int): Sets the maximum size of memory to store a state vector. If a state vector needs more, an error is thrown. In general, a state vector of n-qubits uses 2^n complex values (16 Bytes). If set to 0, the maximum will be automatically set to half the system memory size (Default: 0). * "optimize_ideal_threshold" (int): Sets the qubit threshold for applying circuit optimization passes on ideal circuits. Passes include gate fusion and truncation of unused qubits (Default: 5). * "optimize_noise_threshold" (int): Sets the qubit threshold for applying circuit optimization passes on ideal circuits. Passes include gate fusion and truncation of unused qubits (Default: 12). "statevector" method options ---------------------------- * "statevector_parallel_threshold" (int): Sets the threshold that "n_qubits" must be greater than to enable OpenMP parallelization for matrix multiplication during execution of an experiment. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. Note that setting this too low can reduce performance (Default: 14). * "statevector_sample_measure_opt" (int): Sets the threshold that the number of qubits must be greater than to enable a large qubit optimized implementation of measurement sampling. Note that setting this two low can reduce performance (Default: 10) "stabilizer" method options --------------------------- * "stabilizer_max_snapshot_probabilities" (int): (Default: 32) "extended_stabilizer" method options ------------------------------------ * "extended_stabilizer_measure_sampling" (bool): Enable measure sampling optimization on supported circuits. This prevents the simulator from re-running the measure monte-carlo step for each shot. Enabling measure sampling may reduce accuracy of the measurement counts if the output distribution is strongly peaked. (Default: False) * "extended_stabilizer_mixing_time" (int): Set how long the monte-carlo method runs before performing measurements. If the output distribution is strongly peaked, this can be decreased alongside setting extended_stabilizer_disable_measurement_opt to True. (Default: 5000) * "extended_stabilizer_approximation_error" (double): Set the error in the approximation for the extended_stabilizer method. A smaller error needs more memory and computational time. (Default: 0.05) * "extended_stabilizer_norm_estimation_samples" (int): Number of samples used to compute the correct normalisation for a statevector snapshot. (Default: 100) * "extended_stabilizer_parallel_threshold" (int): Set the minimum size of the extended stabilizer decomposition before we enable OpenMP parallelisation. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. (Default: 100) """ MAX_QUBIT_MEMORY = int( log2(local_hardware_info()['memory'] * (1024**3) / 16)) DEFAULT_CONFIGURATION = { 'backend_name': 'qasm_simulator', 'backend_version': __version__, 'n_qubits': MAX_QUBIT_MEMORY, 'url': 'https://github.com/Qiskit/qiskit-aer', 'simulator': True, 'local': True, 'conditional': True, 'open_pulse': False, 'memory': True, 'max_shots': 100000, 'description': 'A C++ simulator with realistic noise for qobj files', 'coupling_map': None, 'basis_gates': [ 'u1', 'u2', 'u3', 'cx', 'cz', 'cu1', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap', 'multiplexer', 'snapshot', 'unitary', 'reset', 'initialize', 'kraus', 'roerror' ], 'gates': [{ 'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO' }] } def __init__(self, configuration=None, provider=None): super().__init__(qasm_controller_execute, BackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION), provider=provider) def _validate(self, qobj, backend_options, noise_model): """Semantic validations of the qobj which cannot be done via schemas. 1. Check number of qubits will fit in local memory. 2. warn if no classical registers or measurements in circuit. """ clifford_instructions = [ "id", "x", "y", "z", "h", "s", "sdg", "CX", "cx", "cz", "swap", "barrier", "reset", "measure", 'roerror' ] unsupported_ch_instructions = ["u2", "u3", "cu1"] # Check if noise model is Clifford: method = "automatic" if backend_options and "method" in backend_options: method = backend_options["method"] clifford_noise = (method != "statevector") if clifford_noise: if method != "stabilizer" and noise_model: for error in noise_model.to_dict()['errors']: if error['type'] == 'qerror': for circ in error["instructions"]: for instr in circ: if instr not in clifford_instructions: clifford_noise = False break # Check to see if experiments are clifford for experiment in qobj.experiments: name = experiment.header.name # Check for classical bits if experiment.config.memory_slots == 0: logger.warning( 'No classical registers in circuit "%s": ' 'result data will not contain counts.', name) # Check if Clifford circuit or if measure opts missing no_measure = True ch_supported = False ch_supported = method in ["extended_stabilizer", "automatic"] clifford = False if method == "statevector" else clifford_noise for op in experiment.instructions: if not clifford and not no_measure: break # we don't need to check any more ops if clifford and op.name not in clifford_instructions: clifford = False if no_measure and op.name == "measure": no_measure = False if ch_supported and op.name in unsupported_ch_instructions: ch_supported = False # Print warning if clbits but no measure if no_measure: logger.warning( 'No measurements in circuit "%s": ' 'count data will return all zeros.', name) # Check qubits for statevector simulation if not clifford and method != "extended_stabilizer": n_qubits = experiment.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: system_memory = int(local_hardware_info()['memory']) err_string = ('Number of qubits ({}) is greater than ' 'maximum ({}) for "{}" (method=statevector) ' 'with {} GB system memory') err_string = err_string.format(n_qubits, max_qubits, self.name(), system_memory) if method != "automatic": raise AerError(err_string + '.') if n_qubits > 63: raise AerError('{}, and has too many qubits to fall ' 'back to the extended_stabilizer ' 'method.'.format(err_string)) if not ch_supported: raise AerError( '{}, and contains instructions ' 'not supported by the extended_etabilizer ' 'method.'.format(err_string)) logger.info( 'The QasmSimulator will automatically ' 'switch to the Extended Stabilizer backend, based on ' 'the memory requirements.')
def _validate(self, qobj, backend_options, noise_model): """Semantic validations of the qobj which cannot be done via schemas. 1. Check number of qubits will fit in local memory. 2. warn if no classical registers or measurements in circuit. """ clifford_instructions = [ "id", "x", "y", "z", "h", "s", "sdg", "CX", "cx", "cz", "swap", "barrier", "reset", "measure", 'roerror' ] unsupported_ch_instructions = ["u2", "u3", "cu1"] # Check if noise model is Clifford: method = "automatic" if backend_options and "method" in backend_options: method = backend_options["method"] clifford_noise = (method != "statevector") if clifford_noise: if method != "stabilizer" and noise_model: for error in noise_model.to_dict()['errors']: if error['type'] == 'qerror': for circ in error["instructions"]: for instr in circ: if instr not in clifford_instructions: clifford_noise = False break # Check to see if experiments are clifford for experiment in qobj.experiments: name = experiment.header.name # Check for classical bits if experiment.config.memory_slots == 0: logger.warning( 'No classical registers in circuit "%s": ' 'result data will not contain counts.', name) # Check if Clifford circuit or if measure opts missing no_measure = True ch_supported = False ch_supported = method in ["extended_stabilizer", "automatic"] clifford = False if method == "statevector" else clifford_noise for op in experiment.instructions: if not clifford and not no_measure: break # we don't need to check any more ops if clifford and op.name not in clifford_instructions: clifford = False if no_measure and op.name == "measure": no_measure = False if ch_supported and op.name in unsupported_ch_instructions: ch_supported = False # Print warning if clbits but no measure if no_measure: logger.warning( 'No measurements in circuit "%s": ' 'count data will return all zeros.', name) # Check qubits for statevector simulation if not clifford and method != "extended_stabilizer": n_qubits = experiment.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: system_memory = int(local_hardware_info()['memory']) err_string = ('Number of qubits ({}) is greater than ' 'maximum ({}) for "{}" (method=statevector) ' 'with {} GB system memory') err_string = err_string.format(n_qubits, max_qubits, self.name(), system_memory) if method != "automatic": raise AerError(err_string + '.') if n_qubits > 63: raise AerError('{}, and has too many qubits to fall ' 'back to the extended_stabilizer ' 'method.'.format(err_string)) if not ch_supported: raise AerError( '{}, and contains instructions ' 'not supported by the extended_etabilizer ' 'method.'.format(err_string)) logger.info( 'The QasmSimulator will automatically ' 'switch to the Extended Stabilizer backend, based on ' 'the memory requirements.')
class DmSimulatorPy(BaseBackend): """Python implementation of a Density Matrix simulator.""" MAX_QUBITS_MEMORY = int( log2(local_hardware_info()['memory'] * (1024**3) / 16)) DEFAULT_CONFIGURATION = { 'backend_name': 'dm_simulator', 'backend_version': '2.0.0', 'n_qubits': min(24, MAX_QUBITS_MEMORY), 'url': 'https://github.com/Qiskit/qiskit-terra', 'simulator': True, 'local': True, 'conditional': True, 'open_pulse': False, 'memory': True, 'max_shots': 65536, 'coupling_map': None, 'description': 'A python simulator for qasm experiments', 'basis_gates': ['u1', 'u2', 'u3', 'cx', 'id', 'unitary'], 'gates': [{ 'name': 'u1', 'parameters': ['lambda'], 'qasm_def': 'gate u1(lambda) q { U(0,0,lambda) q; }' }, { 'name': 'u2', 'parameters': ['phi', 'lambda'], 'qasm_def': 'gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }' }, { 'name': 'u3', 'parameters': ['theta', 'phi', 'lambda'], 'qasm_def': 'gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }' }, { 'name': 'cx', 'parameters': ['c', 't'], 'qasm_def': 'gate cx c,t { CX c,t; }' }, { 'name': 'id', 'parameters': ['a'], 'qasm_def': 'gate id a { U(0,0,0) a; }' }, { 'name': 'unitary', 'parameters': ['matrix'], 'qasm_def': 'unitary(matrix) q1, q2,...' }] } DEFAULT_OPTIONS = {"initial_densitymatrix": None, "chop_threshold": 1e-15} # Class level variable to return the final state at the end of simulation # This should be set to True for the densitymatrix simulator SHOW_FINAL_STATE = True def __init__(self, configuration=None, provider=None): super().__init__(configuration=(configuration or QasmBackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION)), provider=provider) # Define attributes in __init__. self._local_random = np.random.RandomState() self._classical_memory = 0 self._classical_register = 0 self._densitymatrix = 0 self._probability_of_zero = 0.0 self._number_of_cmembits = 0 self._number_of_qubits = 0 self._shots = 0 self._memory = False self._custom_densitymatrix = None self._initial_densitymatrix = self.DEFAULT_OPTIONS[ "initial_densitymatrix"] self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"] self._qobj_config = None # TEMP self._sample_measure = False def _add_unitary_single(self, gate, params, qubit): """Apply an arbitrary 1-qubit unitary matrix. Args: params (list): list of parameters for U1,U2 and U3 gate. qubit (int): the qubit to apply gate to """ # Getting parameters (theta, phi, lam) = map(float, single_gate_params(gate, params)) print(theta, phi, lam) # changing density matrix self._densitymatrix = np.reshape( self._densitymatrix, (4**qubit, 4, 4**(self._number_of_qubits - qubit - 1))) for j in range(4**(self._number_of_qubits - qubit - 1)): for i in range(4**(qubit)): temp = self._densitymatrix[i, :, j] self._densitymatrix[i, 1, j] = temp[1] * ( np.sin(lam) * np.sin(phi) + np.cos(theta) * np.cos(phi) * np.cos(lam)) + temp[2] * ( np.cos(theta) * np.cos(phi) * np.sin(lam) - np.cos(lam) * np.sin(phi)) + temp[3] * (np.sin(theta) * np.cos(phi)) self._densitymatrix[i, 2, j] = temp[1] * ( np.cos(theta) * np.sin(phi) * np.cos(lam) - np.sin(lam) * np.cos(phi)) + temp[2] * ( np.cos(phi) * np.cos(lam) + np.cos(theta) * np.sin(phi) * np.sin(lam)) + temp[3] * (np.sin(theta) * np.sin(phi)) self._densitymatrix[ i, 3, j] = temp[1] * (-np.cos(lam) * np.sin(theta)) + temp[2] * ( np.sin(theta) * np.sin(lam)) + temp[3] * (np.cos(theta)) self._densitymatrix = np.reshape(self._densitymatrix, 4**(self._number_of_qubits)) def _add_unitary_two(self, gate, qubit0, qubit1): """Apply a two-qubit unitary matrix. Args: gate (matrix_like): a the two-qubit gate matrix qubit0 (int): gate qubit-0 qubit1 (int): gate qubit-1 """ # Compute einsum index string for 1-qubit matrix multiplication indexes = einsum_vecmul_index([qubit0, qubit1], self._number_of_qubits) # Convert to float rank-4 tensor gate_tensor = np.reshape(np.array(gate, dtype=float), 4 * [4]) # Apply matrix multiplication self._densitymatrix = np.einsum(indexes, gate_tensor, self._densitymatrix, dtype=float, casting='no') #self._densitymatrix = np.reshape(self._densitymatrix,()) def _get_measure_outcome(self, qubit): """Simulate the outcome of measurement of a qubit. Args: qubit (int): the qubit to measure Return: tuple: pair (outcome, probability) where outcome is '0' or '1' and probability is the probability of the returned outcome. """ # Axis for numpy.sum to compute probabilities #print('Get_Measure') axis = list(range(self._number_of_qubits)) axis.remove(self._number_of_qubits - 1 - qubit) probabilities = np.sum(np.abs(self._densitymatrix)**2, axis=tuple(axis)) measure_ind = [ x for x in itertools.product([0, 3], repeat=self._number_of_qubits) ] operator_ind = [self._densitymatrix[x] for x in measure_ind] operator_mes = np.array([[1, 1], [1, -1]]) for i in range(self._number_of_qubits - 1): operator_mes = np.kron(np.array([[1, 1], [1, -1]]), operator_mes) probabilities = np.reshape((1 / 2**self._number_of_qubits) * np.array( [np.sum(np.multiply(operator_ind, x)) for x in operator_mes]), self._number_of_qubits * [2]) #print('Probability Before: ', probabilities) probabilities = np.reshape(np.sum(probabilities, axis=tuple(axis)), 2) #print('Probability After: ', probabilities) # Compute einsum index string for 1-qubit matrix multiplication random_number = self._local_random.rand() if random_number < probabilities[0]: return '0', probabilities[0] # Else outcome was '1' return '1', probabilities[1] def _add_ensemble_measure(self): """Perform complete computational basis measurement for current densitymatrix. Args: Returns: list: Complete list of probabilities. """ measure_ind = [ x for x in itertools.product([0, 3], repeat=self._number_of_qubits) ] operator_ind = [self._densitymatrix[x] for x in measure_ind] operator_mes = np.array([[1, 1], [1, -1]]) for i in range(self._number_of_qubits - 1): operator_mes = np.kron(np.array([[1, 1], [1, -1]]), operator_mes) probabilities = np.reshape((0.5**self._number_of_qubits) * np.array( [np.sum(np.multiply(operator_ind, x)) for x in operator_mes]), self._number_of_qubits * [2]) return probabilities def _add_bell_basis_measure(self, qubit_1, qubit_2): """ Apply a Bell basisi measure instruction to two qubits. Post measurement density matrix is returned in the same array. Args: qubit_1 (int): first qubit of Bell pair. qubit_2 (int): second qubit of Bell pair. Returns: Four probabilities in the (|00>+|11>,|00>-|11>,|01>+|10>,|01>-|10>) basis. """ q_1 = min(qubit_1, qubit_2) q_2 = max(qubit_1, qubit_2) #update density matrix self._densitymatrix = np.reshape( self._densitymatrix, (4**(self._number_of_qubits - q_2 - 1), 4, 4 **(q_2 - q_1 - 1), 4, 4**q_1)) bell_probabilities = [0, 0, 0, 0] for i in range(4**(self._number_of_qubits - q_2 - 1)): for j in range(4**(q_2 - q_1 - 1)): for k in range(4**q_1): for l in range(4): for m in range(4): if l != m: self._densitymatrix[i, l, j, m, k] = 0 bell_probabilities[0] += 0.25 * ( self._densitymatrix[i, 0, j, 0, k] + self._densitymatrix[i, 1, j, 1, k] - self._densitymatrix[i, 2, j, 2, k] + self._densitymatrix[i, 3, j, 3, k]) bell_probabilities[1] += 0.25 * ( self._densitymatrix[i, 0, j, 0, k] - self._densitymatrix[i, 1, j, 1, k] + self._densitymatrix[i, 2, j, 2, k] + self._densitymatrix[i, 3, j, 3, k]) bell_probabilities[2] += 0.25 * ( self._densitymatrix[i, 0, j, 0, k] + self._densitymatrix[i, 1, j, 1, k] + self._densitymatrix[i, 2, j, 2, k] - self._densitymatrix[i, 3, j, 3, k]) bell_probabilities[3] += 0.25 * ( self._densitymatrix[i, 0, j, 0, k] - self._densitymatrix[i, 1, j, 1, k] - self._densitymatrix[i, 2, j, 2, k] - self._densitymatrix[i, 3, j, 3, k]) return bell_probabilities def _add_qasm_measure(self, qubit, probability_of_zero): """Apply a computational basis measure instruction to a qubit. Post-measurement density matrix is returned in the same array. Args: qubit (int): qubit is the qubit measured. probability_of_zero (float): is the probability of getting zero state as outcome. """ # update density matrix self._densitymatrix = np.reshape( self._densitymatrix, (4**(qubit), 4, 4**(self._number_of_qubits - qubit - 1))) p_0 = 0.0 p_1 = 0.0 for j in range(4**(self._number_of_qubits - qubit - 1)): for i in range(4**(qubit)): self._densitymatrix[i, 1, j] = 0 self._densitymatrix[i, 2, j] = 0 p_0 += 0.5 * (self._densitymatrix[i, 0, j] + self._densitymatrix[i, 3, j]) p_1 += 0.5 * (self._densitymatrix[i, 0, j] - self._densitymatrix[i, 3, j]) probability_of_zero = p_0 print(p_0, p_1) def _add_qasm_reset(self, qubit): """Apply a reset instruction to a qubit. Args: qubit (int): the qubit being rest This is done by doing a simulating a measurement outcome and projecting onto the outcome state while renormalizing. """ # get measure outcome outcome, probability = self._get_measure_outcome(qubit) # update quantum state if outcome == '0': update = 1 / np.sqrt(probability) * np.array( [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [1, 0, 0, 0]], dtype=float) self._add_unitary_single(update, qubit) else: update = 1 / np.sqrt(probability) * np.array( [[1, 0, 0, -1], [0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 0, -1]], dtype=float) # update classical state self._add_unitary_single(update, qubit) def _validate_initial_densitymatrix(self): """Validate an initial densitymatrix""" # If initial densitymatrix isn't set we don't need to validate if self._initial_densitymatrix is None: return # Check densitymatrix is correct length for number of qubits length = np.size(self._initial_densitymatrix) #print(length, self._number_of_qubits) required_dim = 4**self._number_of_qubits if length != required_dim: raise BasicAerError('initial densitymatrix is incorrect length: ' + '{} != {}'.format(length, required_dim)) # Check if Trace is 0 if self._densitymatrix[0] != 1: raise BasicAerError('Trace of initial densitymatrix is not one: ' + '{} != {}'.format(self._densitymatrix[0], 1)) def _set_options(self, qobj_config=None, backend_options=None): """Set the backend options for all experiments in a qobj""" # Reset default options self._initial_densitymatrix = self.DEFAULT_OPTIONS[ "initial_densitymatrix"] self._chop_threshold = self.DEFAULT_OPTIONS["chop_threshold"] if backend_options is None: backend_options = {} # Check for custom initial densitymatrix in backend_options first, # then config second if 'initial_densitymatrix' in backend_options: self._initial_densitymatrix = np.array( backend_options['initial_densitymatrix'], dtype=float) elif hasattr(qobj_config, 'initial_densitymatrix'): self._initial_densitymatrix = np.array( qobj_config.initial_densitymatrix, dtype=float) if 'custom_densitymatrix' in backend_options: self._custom_densitymatrix = backend_options[ 'custom_densitymatrix'] #if self._initial_densitymatrix is not None and not isinstance(self._initial_densitymatrix, str): # Check the initial densitymatrix is normalized # norm = np.linalg.norm(self._initial_densitymatrix) # if round(norm, 12) != 1: # raise BasicAerError('initial densitymatrix is not normalized: ' + 'norm {} != 1'.format(norm)) print(self._initial_densitymatrix, self._number_of_qubits) # Check for custom chop threshold # Replace with custom options if 'chop_threshold' in backend_options: self._chop_threshold = backend_options['chop_threshold'] elif hasattr(qobj_config, 'chop_threshold'): self._chop_threshold = qobj_config.chop_threshold def _initialize_densitymatrix(self): """Set the initial densitymatrix for simulation""" if self._initial_densitymatrix is None and self._custom_densitymatrix is None: self._densitymatrix = 0.5 * np.array([1, 0, 0, 1], dtype=float) for i in range(self._number_of_qubits - 1): self._densitymatrix = 0.5 * np.kron([1, 0, 0, 1], self._densitymatrix) elif self._initial_densitymatrix is None and self._custom_densitymatrix == 'max_mixed': self._densitymatrix = 0.5 * np.array([1, 0, 0, 0], dtype=float) for i in range(self._number_of_qubits - 1): self._densitymatrix = 0.5 * np.kron([1, 0, 0, 0], self._densitymatrix) elif self._initial_densitymatrix is None and self._custom_densitymatrix == 'uniform_superpos': self._densitymatrix = 0.5 * np.array([1, 1, 0, 0], dtype=float) for i in range(self._number_of_qubits - 1): self._densitymatrix = 0.5 * np.kron([1, 1, 0, 0], self._densitymatrix) else: self._densitymatrix = self._initial_densitymatrix.copy() # Reshape to rank-N tensor # self._densitymatrix = np.reshape(self._densitymatrix, # self._number_of_qubits * [4]) def _get_densitymatrix(self): """Return the current densitymatrix in JSON Result spec format""" vec = np.reshape(self._densitymatrix.real, 4**self._number_of_qubits) # Expand float numbers # Truncate small values vec[abs(vec) < self._chop_threshold] = 0.0 return vec def _validate_measure_sampling(self, experiment): """Determine if measure sampling is allowed for an experiment Args: experiment (QobjExperiment): a qobj experiment. """ # If shots=1 we should disable measure sampling. # This is also required for densitymatrix simulator to return the # correct final densitymatrix without silently dropping final measurements. if self._shots <= 1: self._sample_measure = False return # Check for config flag if hasattr(experiment.config, 'allows_measure_sampling'): self._sample_measure = experiment.config.allows_measure_sampling # If flag isn't found do a simple test to see if a circuit contains # no reset instructions, and no gates instructions after # the first measure. else: measure_flag = False for instruction in experiment.instructions: # If circuit contains reset operations we cannot sample if instruction.name == "reset": self._sample_measure = False return # If circuit contains a measure option then we can # sample only if all following operations are measures if measure_flag: # If we find a non-measure instruction # we cannot do measure sampling if instruction.name not in [ "measure", "barrier", "id", "u0" ]: self._sample_measure = False return elif instruction.name == "measure": measure_flag = True # If we made it to the end of the circuit without returning # measure sampling is allowed self._sample_measure = True def run(self, qobj, backend_options=None): """Run qobj asynchronously. Args: qobj (Qobj): payload of the experiment backend_options (dict): backend options Returns: BasicAerJob: derived from BaseJob Additional Information: backend_options: Is a dict of options for the backend. It may contain * "initial_densitymatrix": vector_like The "initial_densitymatrix" option specifies a custom initial initial densitymatrix for the simulator to be used instead of the all zero state. This size of this vector must be correct for the number of qubits in all experiments in the qobj. Example:: backend_options = { "initial_densitymatrix": np.array([1, 0, 0, 1j]) / np.sqrt(2), } """ self._set_options(qobj_config=qobj.config, backend_options=backend_options) job_id = str(uuid.uuid4()) job = BasicAerJob(self, job_id, self._run_job, qobj) job.submit() return job def _run_job(self, job_id, qobj): """Run experiments in qobj Args: job_id (str): unique id for the job. qobj (Qobj): job description Returns: Result: Result object """ self._validate(qobj) result_list = [] self._shots = qobj.config.shots self._memory = getattr(qobj.config, 'memory', False) self._qobj_config = qobj.config start = time.time() for experiment in qobj.experiments: result_list.append(self.run_experiment(experiment)) end = time.time() result = { 'backend_name': self.name(), 'backend_version': self._configuration.backend_version, 'qobj_id': qobj.qobj_id, 'job_id': job_id, 'results': result_list, 'status': 'COMPLETED', 'success': True, 'time_taken': (end - start), 'header': qobj.header.as_dict() } return Result.from_dict(result) def run_experiment(self, experiment): """Run an experiment (circuit) and return a single experiment result. Args: experiment (QobjExperiment): experiment from qobj experiments list Returns: dict: A result dictionary which looks something like:: { "name": name of this experiment (obtained from qobj.experiment header) "seed": random seed used for simulation "shots": number of shots used in the simulation "data": { "counts": {'0x9: 5, ...}, "memory": ['0x9', '0xF', '0x1D', ..., '0x9'] }, "status": status string for the simulation "success": boolean "time_taken": simulation time of this single experiment } Raises: BasicAerError: if an error occurred. """ start = time.time() self._number_of_qubits = experiment.config.n_qubits self._number_of_cmembits = experiment.config.memory_slots self._densitymatrix = 0 self._classical_memory = 0 self._classical_register = 0 #self._sample_measure = False # Validate the dimension of initial densitymatrix if set self._validate_initial_densitymatrix() # Get the seed looking in circuit, qobj, and then random. if hasattr(experiment.config, 'seed_simulator'): seed_simulator = experiment.config.seed_simulator elif hasattr(self._qobj_config, 'seed_simulator'): seed_simulator = self._qobj_config.seed_simulator else: # For compatibility on Windows force dyte to be int32 ## TODO # and set the maximum value to be (4 ** 31) - 1 seed_simulator = np.random.randint(2147483647, dtype='int32') self._local_random.seed(seed=seed_simulator) # Check if measure sampling is supported for current circuit #self._validate_measure_sampling(experiment) # List of final counts for all shots memory = [] # Check if we can sample measurements, if so we only perform 1 shot # and sample all outcomes from the final state vector if self._sample_measure: shots = 1 # Store (qubit, cmembit) pairs for all measure ops in circuit to # be sampled measure_sample_ops = [] else: shots = self._shots #print("No error till now") #np.asarray() #print(experiment.instructions) for _ in range(shots): self._initialize_densitymatrix() # Initialize classical memory to all 0 self._classical_memory = 0 self._classical_register = 0 #print(self._densitymatrix) for operation in experiment.instructions: conditional = getattr(operation, 'conditional', None) if isinstance(conditional, int): conditional_bit_set = ( self._classical_register >> conditional) & 1 if not conditional_bit_set: continue elif conditional is not None: mask = int(operation.conditional.mask, 16) if mask > 0: value = self._classical_memory & mask while (mask & 0x1) == 0: mask >>= 1 value >>= 1 if value != int(operation.conditional.val, 16): continue # Check if single gate #print(self._initial_densitymatrix) #print(self._densitymatrix) #print('Operation: ', operation.name) if operation.name in ('U', 'u1', 'u2', 'u3'): params = getattr(operation, 'params', None) qubit = operation.qubits[0] #gate = single_gate_dm_matrix(operation.name, params) self._add_unitary_single(operation.name, params, qubit) # Check if CX gate elif operation.name in ('id', 'u0'): pass elif operation.name in ('CX', 'cx'): qubit0 = operation.qubits[0] qubit1 = operation.qubits[1] #gate = cx_gate_dm_matrix() self._add_unitary_two(qubit0, qubit1) # Check if reset elif operation.name == 'reset': qubit = operation.qubits[0] self._add_qasm_reset(qubit) # Check if barrier elif operation.name == 'barrier': pass # Check if measure elif operation.name == 'measure': qubit = operation.qubits[0] cmembit = operation.memory[0] cregbit = operation.register[0] if hasattr( operation, 'register') else None if self._sample_measure: # If sampling measurements record the qubit and cmembit # for this measurement for later sampling measure_sample_ops.append((qubit, cmembit)) else: # If not sampling perform measurement as normal self._add_qasm_measure(qubit, self._probability_of_zero) elif operation.name == 'bfunc': mask = int(operation.mask, 16) relation = operation.relation val = int(operation.val, 16) cregbit = operation.register cmembit = operation.memory if hasattr(operation, 'memory') else None compared = (self._classical_register & mask) - val if relation == '==': outcome = (compared == 0) elif relation == '!=': outcome = (compared != 0) elif relation == '<': outcome = (compared < 0) elif relation == '<=': outcome = (compared <= 0) elif relation == '>': outcome = (compared > 0) elif relation == '>=': outcome = (compared >= 0) else: raise BasicAerError( 'Invalid boolean function relation.') # Store outcome in register and optionally memory slot regbit = 1 << cregbit self._classical_register = \ (self._classical_register & (~regbit)) | (int(outcome) << cregbit) if cmembit is not None: membit = 1 << cmembit self._classical_memory = \ (self._classical_memory & (~membit)) | (int(outcome) << cmembit) else: backend = self.name() err_msg = '{0} encountered unrecognized operation "{1}"' raise BasicAerError(err_msg.format(backend, operation.name)) # Add final creg data to memory list if self._number_of_cmembits > 0: if self._sample_measure: # If sampling we generate all shot samples from the final densitymatrix memory = self._add_sample_measure(measure_sample_ops, self._shots) else: # Turn classical_memory (int) into bit string and pad zero for unused cmembits outcome = bin(self._classical_memory)[2:] memory.append(hex(int(outcome, 2))) # Add data data = {'counts': dict(Counter(memory))} # Optionally add memory list if self._memory: data['memory'] = memory # Optionally add final densitymatrix if self.SHOW_FINAL_STATE: data['densitymatrix'] = self._get_densitymatrix() # Remove empty counts and memory for densitymatrix simulator if not data['counts']: data.pop('counts') if 'memory' in data and not data['memory']: data.pop('memory') end = time.time() return { 'name': experiment.header.name, 'seed_simulator': seed_simulator, 'shots': self._shots, 'data': data, 'status': 'DONE', 'success': True, 'time_taken': (end - start), 'header': experiment.header.as_dict() } def _validate(self, qobj): """Semantic validations of the qobj which cannot be done via schemas.""" n_qubits = qobj.config.n_qubits max_qubits = self.configuration().n_qubits if n_qubits > max_qubits: raise BasicAerError( 'Number of qubits {} '.format(n_qubits) + 'is greater than maximum ({}) '.format(max_qubits) + 'for "{}".'.format(self.name())) for experiment in qobj.experiments: name = experiment.header.name if experiment.config.memory_slots == 0: logger.warning( 'No classical registers in circuit "%s", ' 'counts will be empty.', name) elif 'measure' not in [op.name for op in experiment.instructions]: logger.warning( 'No measurements in circuit "%s", ' 'classical register will remain all zeros.', name)
class QasmSimulator(AerBackend): """Aer quantum circuit simulator Backend options: The following backend options may be used with in the `backend_options` kwarg diction for `QasmSimulator.run` or `qiskit.execute` Simulation method option ------------------------ * "method" (str): Set the simulation method. Allowed values are: * "statevector": Uses a dense statevector simulation. * "stabilizer": Uses a Clifford stabilizer state simulator that is only valid for Clifford circuits and noise models. * "extended_stabilizer": Uses an approximate simulator that decomposes circuits into stabilizer state terms, the number of which grows with the number of non-Clifford gates. * "matrix_product_state": Uses a Matrix Product State (MPS) simulator. * "cyclops_statevector": Uses a dense statevector simulation parallelized by Cyclops Tensor Framework (CTF). * "automatic": Automatically run on stabilizer simulator if the circuit and noise model supports it. If there is enough available memory, uses the statevector method. Otherwise, uses the extended_stabilizer method (Default: "automatic"). General options --------------- * "zero_threshold" (double): Sets the threshold for truncating small values to zero in the result data (Default: 1e-10). * "validation_threshold" (double): Sets the threshold for checking if initial states are valid (Default: 1e-8). * "max_parallel_threads" (int): Sets the maximum number of CPU cores used by OpenMP for parallelization. If set to 0 the maximum will be set to the number of CPU cores (Default: 0). * "max_parallel_experiments" (int): Sets the maximum number of qobj experiments that may be executed in parallel up to the max_parallel_threads value. If set to 1 parallel circuit execution will be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads (Default: 1). * "max_parallel_shots" (int): Sets the maximum number of shots that may be executed in parallel during each experiment execution, up to the max_parallel_threads value. If set to 1 parallel shot execution wil be disabled. If set to 0 the maximum will be automatically set to max_parallel_threads. Note that this cannot be enabled at the same time as parallel experiment execution (Default: 1). * "max_memory_mb" (int): Sets the maximum size of memory to store a state vector. If a state vector needs more, an error is thrown. In general, a state vector of n-qubits uses 2^n complex values (16 Bytes). If set to 0, the maximum will be automatically set to half the system memory size (Default: 0). * "optimize_ideal_threshold" (int): Sets the qubit threshold for applying circuit optimization passes on ideal circuits. Passes include gate fusion and truncation of unused qubits (Default: 5). * "optimize_noise_threshold" (int): Sets the qubit threshold for applying circuit optimization passes on ideal circuits. Passes include gate fusion and truncation of unused qubits (Default: 12). "statevector" method options ---------------------------- * "statevector_parallel_threshold" (int): Sets the threshold that "n_qubits" must be greater than to enable OpenMP parallelization for matrix multiplication during execution of an experiment. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. Note that setting this too low can reduce performance (Default: 14). * "statevector_sample_measure_opt" (int): Sets the threshold that the number of qubits must be greater than to enable a large qubit optimized implementation of measurement sampling. Note that setting this two low can reduce performance (Default: 10) "stabilizer" method options --------------------------- * "stabilizer_max_snapshot_probabilities" (int): (Default: 32) "extended_stabilizer" method options ------------------------------------ * "extended_stabilizer_measure_sampling" (bool): Enable measure sampling optimization on supported circuits. This prevents the simulator from re-running the measure monte-carlo step for each shot. Enabling measure sampling may reduce accuracy of the measurement counts if the output distribution is strongly peaked. (Default: False) * "extended_stabilizer_mixing_time" (int): Set how long the monte-carlo method runs before performing measurements. If the output distribution is strongly peaked, this can be decreased alongside setting extended_stabilizer_disable_measurement_opt to True. (Default: 5000) * "extended_stabilizer_approximation_error" (double): Set the error in the approximation for the extended_stabilizer method. A smaller error needs more memory and computational time. (Default: 0.05) * "extended_stabilizer_norm_estimation_samples" (int): Number of samples used to compute the correct normalisation for a statevector snapshot. (Default: 100) * "extended_stabilizer_parallel_threshold" (int): Set the minimum size of the extended stabilizer decomposition before we enable OpenMP parallelisation. If parallel circuit or shot execution is enabled this will only use unallocated CPU cores up to max_parallel_threads. (Default: 100) """ MAX_QUBIT_MEMORY = int( log2(local_hardware_info()['memory'] * (1024**3) / 16)) DEFAULT_CONFIGURATION = { 'backend_name': 'qasm_simulator', 'backend_version': __version__, 'n_qubits': MAX_QUBIT_MEMORY, 'url': 'https://github.com/Qiskit/qiskit-aer', 'simulator': True, 'local': True, 'conditional': True, 'open_pulse': False, 'memory': True, 'max_shots': 100000, 'description': 'A C++ simulator with realistic noise for qobj files', 'coupling_map': None, 'basis_gates': [ 'u1', 'u2', 'u3', 'cx', 'cz', 'cu1', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 't', 'tdg', 'ccx', 'swap', 'multiplexer', 'snapshot', 'unitary', 'reset', 'initialize', 'kraus', 'roerror' ], 'gates': [{ 'name': 'TODO', 'parameters': [], 'qasm_def': 'TODO' }] } def __init__(self, configuration=None, provider=None): super().__init__(qasm_controller_execute, QasmBackendConfiguration.from_dict( self.DEFAULT_CONFIGURATION), provider=provider) def _validate(self, qobj, backend_options, noise_model): """Semantic validations of the qobj which cannot be done via schemas. Warn if no measurements in circuit with classical registers. """ for experiment in qobj.experiments: # If circuit contains classical registers but not # measurements raise a warning if experiment.config.memory_slots > 0: # Check if measure opts missing no_measure = True for op in experiment.instructions: if not no_measure: break # we don't need to check any more ops if no_measure and op.name == "measure": no_measure = False # Print warning if clbits but no measure if no_measure: logger.warning( 'No measurements in circuit "%s": ' 'count data will return all zeros.', experiment.header.name)