Skip to content

Commit

Permalink
Merge ebe2449 into e623e4f
Browse files Browse the repository at this point in the history
  • Loading branch information
QFer committed Apr 19, 2022
2 parents e623e4f + ebe2449 commit 57fa9df
Show file tree
Hide file tree
Showing 27 changed files with 915 additions and 345 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ matrix:
if: branch = dev
dist: focal
install:
- python -m pip install -U pip setuptools
- pip install --upgrade .[qiskit,projectq,dev] coveralls
env:
- API_URL=https://staging.quantum-inspire.com
Expand Down
5 changes: 2 additions & 3 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
https://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

Expand Down Expand Up @@ -175,5 +175,4 @@
of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS



5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,6 @@ qubits 2
H q[0]
CNOT q[0], q[1]
Measure q[0,1]
'''

Expand Down Expand Up @@ -218,9 +217,9 @@ qi = QuantumInspireAPI()
To create a token authentication object yourself using the stored token you do:
```python
from quantuminspire.credentials import get_authentication
auth = get_authentication()
authentication = get_authentication()
```
This `auth` can then be used to initialize the Quantum Inspire API object.
This `authentication` can then be used to initialize the Quantum Inspire API object.

## Testing

Expand Down
5 changes: 2 additions & 3 deletions docs/introduction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ API wrapper directly:
H q[0]
CNOT q[0], q[1]
Measure q[0,1]
'''
Expand Down Expand Up @@ -256,9 +255,9 @@ you do:
.. code:: python
from quantuminspire.credentials import get_authentication
auth = get_authentication()
authentication = get_authentication()
This ``auth`` can then be used to initialize the Quantum Inspire API
This ``authentication`` can then be used to initialize the Quantum Inspire API
object.

Testing
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
Expand Down
120 changes: 80 additions & 40 deletions src/quantuminspire/api.py

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions src/quantuminspire/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
Expand Down Expand Up @@ -180,13 +180,13 @@ def get_authentication() -> Union[TokenAuthentication, BasicAuthentication]:
token = load_account()
if token is not None:
return get_token_authentication(token)
else:
email = os.environ.get('QI_EMAIL', None)
password = os.environ.get('QI_PASSWORD', None)
if email is None or password is None:
print('Enter email:')
email = input()
print('Enter password')
password = getpass()

return get_basic_authentication(email, password)

email = os.environ.get('QI_EMAIL', None)
password = os.environ.get('QI_PASSWORD', None)
if email is None or password is None:
print('Enter email:')
email = input()
print('Enter password')
password = getpass()

return get_basic_authentication(email, password)
2 changes: 1 addition & 1 deletion src/quantuminspire/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
Expand Down
2 changes: 1 addition & 1 deletion src/quantuminspire/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
Expand Down
71 changes: 41 additions & 30 deletions src/quantuminspire/projectq/backend_qx.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -31,7 +31,7 @@
Measure, Ph, Rx, Ry, Rz, S, Sdag, Swap, T, Tdag, X,
Y, Z, Command, CZ, C, R, CNOT, Toffoli)
from projectq.types import Qubit
from quantuminspire.api import QuantumInspireAPI
from quantuminspire.api import QuantumInspireAPI, V1_MEASUREMENT_BLOCK_INDEX
from quantuminspire.exceptions import AuthenticationError
from quantuminspire.exceptions import ProjectQBackendError
# shortcut for Controlled Phase-shift gate (CR)
Expand Down Expand Up @@ -76,7 +76,7 @@ def __init__(self, num_runs: int = 1024, verbose: int = 0, quantum_inspire_api:
'provide a QuantumInspireAPI instance as parameter to QIBackend') from ex
self._quantum_inspire_api: QuantumInspireAPI = quantum_inspire_api
self._backend_type: Dict[str, Any] = self._quantum_inspire_api.get_backend_type(backend_type)
if num_runs < 1 or num_runs > self._backend_type['max_number_of_shots']:
if num_runs < 1 or num_runs > self._backend_type["max_number_of_shots"]:
raise ProjectQBackendError(f'Invalid number of runs (num_runs={num_runs})')
self._num_runs: int = num_runs
self._full_state_projection = not self._backend_type["is_hardware_backend"]
Expand All @@ -85,16 +85,23 @@ def __init__(self, num_runs: int = 1024, verbose: int = 0, quantum_inspire_api:
self._one_qubit_gates: Tuple[Any, ...] = self._get_one_qubit_gates()
self._two_qubit_gates: Tuple[Any, ...] = self._get_two_qubit_gates()
self._three_qubit_gates: Tuple[Any, ...] = self._get_three_qubit_gates()
self._multiple_measurements = "flags" in self._backend_type and "multiple_measurement" in \
self._backend_type["flags"]
self._parallel_computing = "flags" in self._backend_type and "parallel_computing" in \
self._backend_type["flags"]

def _get_one_qubit_gates(self) -> Tuple[Any, ...]:
allowed_operations = self._backend_type['allowed_operations']
if len(allowed_operations) > 0:
one_qubit_gates = []
for gate_set in ['single_gates', 'parameterized_single_gates']:
for gate_set in ['single_gates', 'parameterized_single_gates', 'wait', 'barrier']:
if gate_set in allowed_operations:
for gate in allowed_operations[gate_set]:
if gate in ['x', 'y', 'z', 'h', 's', 'sdag', 't', 'tdag', 'rx', 'ry', 'rz']:
if gate in ['x', 'y', 'z', 'h', 's', 'sdag', 't', 'tdag', 'rx', 'ry', 'rz', 'barrier']:
one_qubit_gates += [getattr(sys.modules[__name__], gate.capitalize())]
if gate == 'wait':
if self._verbose >= 3:
print(f"ProjectQ doesn't have an equivalent gate for cQASM gate '{gate}'")
else:
one_qubit_gates = [X, Y, Z, H, S, Sdag, T, Tdag, Rx, Ry, Rz]
return tuple(one_qubit_gates)
Expand Down Expand Up @@ -163,8 +170,6 @@ def is_available(self, cmd: Command) -> bool:
g = cmd.gate
if self._verbose >= 3:
print(f'call to is_available with cmd {cmd} (gate {g})')
if g in (Measure, Allocate, Deallocate, Barrier):
return True
if g == NOT and count == 2:
return Toffoli in self.three_qubit_gates
if g == NOT and count == 1:
Expand All @@ -175,6 +180,10 @@ def is_available(self, cmd: Command) -> bool:
return CR in self.two_qubit_gates
if count != 0:
return False
if g in (Measure, Allocate, Deallocate):
return True
if g == Barrier:
return Barrier in self.one_qubit_gates
if g == Swap:
return g in self.two_qubit_gates
if g in (T, Tdag, S, Sdag, H, X, Y, Z):
Expand Down Expand Up @@ -296,8 +305,8 @@ def _allocate_qubit(self, index_to_add: int) -> None:
self._max_qubit_id = max(self._max_qubit_id, index_to_add)

if self._verbose >= 1:
print(f'_store: Allocate gate {(index_to_add,)}')
print(f' _allocation_map {self._allocation_map}')
print(f"_store: Allocate gate {(index_to_add,)}")
print(f" _allocation_map {self._allocation_map}")

def _deallocate_qubit(self, index_to_remove: int) -> None:
""" De-allocate qubit.
Expand All @@ -316,8 +325,8 @@ def _deallocate_qubit(self, index_to_remove: int) -> None:
self._allocation_map[index] = (allocation_entry[0], -1)

if self._verbose >= 1:
print(f'_store: Deallocate gate {(index_to_remove,)}')
print(f' _allocation_map {self._allocation_map}')
print(f"_store: Deallocate gate {(index_to_remove,)}")
print(f" _allocation_map {self._allocation_map}")

def _physical_to_simulated(self, physical_qubit_id: int) -> int:
"""
Expand Down Expand Up @@ -359,7 +368,7 @@ def _store(self, cmd: Command) -> None:
:param cmd: Command to store.
"""
if self._verbose >= 3:
print(f'_store {id(self)}: cmd {cmd}')
print(f"_store {id(self)}: cmd {cmd}")

if self._clear:
self._clear = False
Expand Down Expand Up @@ -397,8 +406,13 @@ def _store(self, cmd: Command) -> None:
self._measured_ids += [logical_id]
# do not add the measurement statement when fsp is possible
if not self._full_state_projection:
if self._is_simulation_backend:
self._qasm += f"\nmeasure q[{sim_qubit_id}]"
self._qasm += f"\nmeasure q[{sim_qubit_id}]"
return

if gate == Barrier:
qb_pos_list = [qb.id for qr in cmd.qubits for qb in qr]
qb_str = ','.join([f"{self._physical_to_simulated(x)}" for x in qb_pos_list])
self._qasm += f"\nbarrier q[{qb_str}]"
return

# when we find a gate after measurements we don't have fsp
Expand All @@ -424,17 +438,13 @@ def _store(self, cmd: Command) -> None:
ctrl_pos = self._physical_to_simulated(cmd.control_qubits[0].id)
qb_pos = self._physical_to_simulated(cmd.qubits[0][0].id)
self._qasm += f"\ncz q[{ctrl_pos}], q[{qb_pos}]"
elif gate == Barrier:
qb_pos_list = [qb.id for qr in cmd.qubits for qb in qr]
qb_str = ', '.join([f'q[{self._physical_to_simulated(x)}]' for x in qb_pos_list])
self._qasm += f"\n# barrier gate {qb_str};"
elif isinstance(gate, (Rz, R)) and get_control_count(cmd) == 1:
ctrl_pos = self._physical_to_simulated(cmd.control_qubits[0].id)
qb_pos = self._physical_to_simulated(cmd.qubits[0][0].id)
gate_name = 'cr'
self._qasm += f"\n{gate_name} q[{ctrl_pos}],q[{qb_pos}],{gate.angle:.12f}"
elif isinstance(gate, (Rx, Ry)) and get_control_count(cmd) == 1:
raise NotImplementedError('controlled Rx or Ry gate not implemented')
raise NotImplementedError("controlled Rx or Ry gate not implemented")
elif isinstance(gate, (Rx, Ry, Rz)):
assert get_control_count(cmd) == 0
qb_pos = self._physical_to_simulated(cmd.qubits[0][0].id)
Expand All @@ -446,13 +456,13 @@ def _store(self, cmd: Command) -> None:
elif gate == Sdag and get_control_count(cmd) == 0:
qb_pos = self._physical_to_simulated(cmd.qubits[0][0].id)
self._qasm += f"\nsdag q[{qb_pos}]"
elif isinstance(gate, tuple(type(gate) for gate in (X, Y, Z, H, S, T))):
elif isinstance(gate, tuple(type(gate_in_set) for gate_in_set in (X, Y, Z, H, S, T))):
assert get_control_count(cmd) == 0
gate_str = str(gate).lower()
qb_pos = self._physical_to_simulated(cmd.qubits[0][0].id)
self._qasm += f"\n{gate_str} q[{qb_pos}]"
else:
raise NotImplementedError(f'cmd {(cmd,)} not implemented')
raise NotImplementedError(f"cmd '{(cmd,)}' not implemented")

def _logical_to_physical(self, logical_qubit_id: int) -> int:
"""Return the physical location of the qubit with the given logical id.
Expand Down Expand Up @@ -543,7 +553,7 @@ def _run(self) -> None:
if self._qasm == "":
return

# Finally: add measurement commands for all measured qubits if no measurements are given.
# Finally: add measurement commands for all qubits if no measurements are given.
# Only for simulation backends, measurements after all gate operations will perform properly in FSP.
if not self._measured_ids and self._is_simulation_backend:
self.__add_measure_all_qubits()
Expand All @@ -556,8 +566,8 @@ def _run(self) -> None:

def _finalize_qasm(self) -> None:
""" Finalize qasm (add version and qubits line). """
qasm = f'version 1.0\n# cQASM generated by Quantum Inspire {self.__class__} class\n' \
f'qubits {self._number_of_qubits}\n'
qasm = f"version 1.0\n# cQASM generated by Quantum Inspire {self.__class__} class\n" \
f"qubits {self._number_of_qubits}\n"
qasm += self._qasm

if self._verbose >= 2:
Expand All @@ -578,19 +588,20 @@ def _execute_cqasm(self) -> None:
full_state_projection=self._full_state_projection
)

if not self._quantum_inspire_result.get('histogram', {}):
raw_text = self._quantum_inspire_result.get('raw_text', 'no raw_text in result structure')
if not self._quantum_inspire_result.get("histogram", []):
raw_text = self._quantum_inspire_result.get("raw_text", "no raw_text in result structure")
raise ProjectQBackendError(
f'Result structure does not contain proper histogram. raw_text field: {raw_text}')
f"Result structure does not contain proper histogram. raw_text field: {raw_text}")

def _filter_result_by_measured_qubits(self) -> None:
""" Filters the raw result by collapsing states so that unmeasured qubits are ignored.
Populates :attr:`_measured_states` by filtering :attr:`_quantum_inspire_result['histogram']` based on
:attr:`_measured_ids` (which are supposed to be logical qubit id's).
Populates :attr:`_measured_states` by filtering :attr:`_quantum_inspire_result['histogram']` of the last
measurement block based on :attr:`_measured_ids` (which are supposed to be logical qubit id's).
"""
mask_bits = map(lambda bit: self._physical_to_simulated(self._logical_to_physical(bit)), self._measured_ids)
histogram: Dict[int, float] = {int(k): v for k, v in self._quantum_inspire_result['histogram'].items()}
histogram: Dict[int, float] = {int(k): v for k, v in
self._quantum_inspire_result["histogram"][V1_MEASUREMENT_BLOCK_INDEX].items()}
self._measured_states = QIBackend._filter_histogram(histogram, mask_bits)

@staticmethod
Expand Down

0 comments on commit 57fa9df

Please sign in to comment.