diff --git a/CHANGELOG.rst b/CHANGELOG.rst index be5a381eb..1c5720cc8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog ========= +Version 20.14 +============= + +* Add ``IQMClient::get_feedback_groups`` method. `#162 `_ + Version 20.13 ============= diff --git a/README.rst b/README.rst index 028159560..a84efdfa6 100644 --- a/README.rst +++ b/README.rst @@ -49,15 +49,16 @@ Run the tests: $ ./test -Update the requirements. This is necessary when you add a new dependency or update an existing one in `pyproject.toml`. -After this, any changes in the lockfile `requirements.txt` have to be committed. -The script upgrades locked dependencies defined in `pyproject.toml` within the given version ranges. However, transitive -dependencies are deliberately not upgraded automatically. +Update the requirements: .. code-block:: bash $ python update-requirements.py +After this, any changes in the lockfile `requirements.txt` have to be committed. +The script upgrades locked dependencies defined in `pyproject.toml` within the given version ranges. However, transitive +dependencies are deliberately not upgraded automatically. + Documentation ============= diff --git a/requirements.txt b/requirements.txt index 4cb6fd222..3016e17f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -329,62 +329,62 @@ mypy==1.7.1 \ mypy-extensions==1.0.0 \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 -numpy==2.2.1 \ - --hash=sha256:059e6a747ae84fce488c3ee397cee7e5f905fd1bda5fb18c66bc41807ff119b2 \ - --hash=sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5 \ - --hash=sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60 \ - --hash=sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71 \ - --hash=sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631 \ - --hash=sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8 \ - --hash=sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2 \ - --hash=sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16 \ - --hash=sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa \ - --hash=sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591 \ - --hash=sha256:3d03883435a19794e41f147612a77a8f56d4e52822337844fff3d4040a142964 \ - --hash=sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821 \ - --hash=sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484 \ - --hash=sha256:4250888bcb96617e00bfa28ac24850a83c9f3a16db471eca2ee1f1714df0f957 \ - --hash=sha256:4511d9e6071452b944207c8ce46ad2f897307910b402ea5fa975da32e0102800 \ - --hash=sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918 \ - --hash=sha256:48fd472630715e1c1c89bf1feab55c29098cb403cc184b4859f9c86d4fcb6a95 \ - --hash=sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0 \ - --hash=sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e \ - --hash=sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d \ - --hash=sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73 \ - --hash=sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59 \ - --hash=sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51 \ - --hash=sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355 \ - --hash=sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348 \ - --hash=sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e \ - --hash=sha256:5edb4e4caf751c1518e6a26a83501fda79bff41cc59dac48d70e6d65d4ec4440 \ - --hash=sha256:61048b4a49b1c93fe13426e04e04fdf5a03f456616f6e98c7576144677598675 \ - --hash=sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84 \ - --hash=sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046 \ - --hash=sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab \ - --hash=sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712 \ - --hash=sha256:7671dc19c7019103ca44e8d94917eba8534c76133523ca8406822efdd19c9308 \ - --hash=sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315 \ - --hash=sha256:7ba9cc93a91d86365a5d270dee221fdc04fb68d7478e6bf6af650de78a8339e3 \ - --hash=sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008 \ - --hash=sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5 \ - --hash=sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2 \ - --hash=sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e \ - --hash=sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7 \ - --hash=sha256:a7746f235c47abc72b102d3bce9977714c2444bdfaea7888d241b4c4bb6a78bf \ - --hash=sha256:aa3017c40d513ccac9621a2364f939d39e550c542eb2a894b4c8da92b38896ab \ - --hash=sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd \ - --hash=sha256:b541032178a718c165a49638d28272b771053f628382d5e9d1c93df23ff58dbf \ - --hash=sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8 \ - --hash=sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb \ - --hash=sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268 \ - --hash=sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d \ - --hash=sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780 \ - --hash=sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716 \ - --hash=sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e \ - --hash=sha256:f62aa6ee4eb43b024b0e5a01cf65a0bb078ef8c395e8713c6e8a12a697144528 \ - --hash=sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af \ - --hash=sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7 \ - --hash=sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51 +numpy==2.2.2 \ + --hash=sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f \ + --hash=sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0 \ + --hash=sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd \ + --hash=sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2 \ + --hash=sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4 \ + --hash=sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648 \ + --hash=sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be \ + --hash=sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb \ + --hash=sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160 \ + --hash=sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd \ + --hash=sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a \ + --hash=sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84 \ + --hash=sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e \ + --hash=sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748 \ + --hash=sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825 \ + --hash=sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60 \ + --hash=sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957 \ + --hash=sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715 \ + --hash=sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317 \ + --hash=sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e \ + --hash=sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283 \ + --hash=sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278 \ + --hash=sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9 \ + --hash=sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de \ + --hash=sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369 \ + --hash=sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb \ + --hash=sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189 \ + --hash=sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014 \ + --hash=sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323 \ + --hash=sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e \ + --hash=sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49 \ + --hash=sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50 \ + --hash=sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d \ + --hash=sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37 \ + --hash=sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39 \ + --hash=sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576 \ + --hash=sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a \ + --hash=sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba \ + --hash=sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7 \ + --hash=sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826 \ + --hash=sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467 \ + --hash=sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495 \ + --hash=sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc \ + --hash=sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391 \ + --hash=sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0 \ + --hash=sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97 \ + --hash=sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c \ + --hash=sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac \ + --hash=sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369 \ + --hash=sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8 \ + --hash=sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2 \ + --hash=sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff \ + --hash=sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a \ + --hash=sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df \ + --hash=sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f packaging==24.2 \ --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f @@ -403,9 +403,9 @@ pluggy==1.5.0 \ prettytable==3.12.0 \ --hash=sha256:77ca0ad1c435b6e363d7e8623d7cc4fcf2cf15513bf77a1c1b2e814930ac57cc \ --hash=sha256:f04b3e1ba35747ac86e96ec33e3bb9748ce08e254dc2a1c6253945901beec804 -pydantic==2.10.5 \ - --hash=sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff \ - --hash=sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53 +pydantic==2.10.6 \ + --hash=sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584 \ + --hash=sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236 pydantic-core==2.27.2 \ --hash=sha256:00bad2484fa6bda1e216e7345a798bd37c68fb2d97558edd584942aa41b7d278 \ --hash=sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50 \ diff --git a/src/iqm/iqm_client/api.py b/src/iqm/iqm_client/api.py index 45bac8bbe..bb75a7a28 100644 --- a/src/iqm/iqm_client/api.py +++ b/src/iqm/iqm_client/api.py @@ -39,6 +39,7 @@ class APIEndpoint(Enum): # Calibration and Calibration Service endpoints CALIBRATION_SERVICE_CONFIGURATION = auto() QUANTUM_ARCHITECTURE = auto() + CHANNEL_PROPERTIES = auto() QUALITY_METRICS_LATEST = auto() QUALITY_METRICS_MONITORING = auto() CALIBRATED_GATES = auto() @@ -133,6 +134,7 @@ def _get_api_urls(self) -> dict[APIEndpoint, str]: APIEndpoint.ABORT_CALIBRATION_JOB: "cocos/jobs/%s/abort", APIEndpoint.DELETE_JOB: "station/circuits/%s", APIEndpoint.QUANTUM_ARCHITECTURE: "cocos/quantum-architecture", + APIEndpoint.CHANNEL_PROPERTIES: "station/channel-properties", APIEndpoint.CALIBRATED_GATES: "cocos/api/v1/calibration/%s/gates", APIEndpoint.QUALITY_METRICS_MONITORING: "cocos/api/v1/monitor/calibration/metrics", APIEndpoint.HEALTH: "cocos/health", diff --git a/src/iqm/iqm_client/iqm_client.py b/src/iqm/iqm_client/iqm_client.py index 24289db46..70b435b5a 100644 --- a/src/iqm/iqm_client/iqm_client.py +++ b/src/iqm/iqm_client/iqm_client.py @@ -887,6 +887,51 @@ def get_dynamic_quantum_architecture( return dqa + def get_feedback_groups(self, *, timeout_secs: float = REQUESTS_TIMEOUT) -> tuple[frozenset[str], ...]: + """Retrieve groups of qubits that can receive real-time feedback signals from each other. + + Real-time feedback enables conditional gates such as `cc_prx`. + Some hardware configurations support routing real-time feedback only between certain qubits. + + This method is only supported for the API variant V2. + + Returns: + Feedback groups. Within a group, any qubit can receive real-time feedback from any other qubit in + the same group. A qubit can belong to multiple groups. + If there is only one group, there are no restrictions regarding feedback routing. + + Raises: + ClientAuthenticationError: if no valid authentication is provided + HTTPException: HTTP exceptions + """ + result = requests.get( + self._api.url(APIEndpoint.CHANNEL_PROPERTIES), + headers=self._default_headers(), + timeout=timeout_secs, + ) + self._check_authentication_errors(result) + result.raise_for_status() + try: + channel_properties = result.json() + except (json.decoder.JSONDecodeError, KeyError) as e: + raise HTTPError(f'Invalid response: {result.text}, {e}') from e + + all_qubits = self.get_quantum_architecture().qubits + groups: dict[str, set[str]] = {} + # All qubits that can read from the same source belong to the same group. + # A qubit may belong to multiple groups. + for channel_name, properties in channel_properties.items(): + # Relying on naming convention because we don't have proper mapping available: + qubit = channel_name.split("__")[0] + if qubit not in all_qubits: + continue + for source in properties.get("fast_feedback_sources", ()): + groups.setdefault(source, set()).add(qubit) + # Merge identical groups + unique_groups: set[frozenset[str]] = {frozenset(group) for group in groups.values()} + # Sort by group size + return tuple(sorted(unique_groups, key=len, reverse=True)) + def close_auth_session(self) -> bool: """Terminate session with authentication server if there was one created. diff --git a/tests/conftest.py b/tests/conftest.py index 7659ac349..95e1398ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -120,6 +120,12 @@ def dynamic_architecture_url(base_url) -> str: return f'{base_url}/api/v1/calibration/default/gates' +@pytest.fixture() +def channel_properties_url(base_url) -> str: + # Only in API V2 + return f'{base_url}/station/channel-properties' + + @pytest.fixture def settings_dict(): """ @@ -445,12 +451,12 @@ def sample_static_architecture(): return { 'quantum_architecture': { 'name': 'hercules', - 'qubits': ['QB1', 'QB2'], + 'qubits': ['QB1', 'QB2', 'QB3'], 'qubit_connectivity': [['QB1', 'QB2']], 'operations': { - 'prx': [['QB1'], ['QB2']], + 'prx': [['QB1'], ['QB2'], ['QB3']], 'cz': [['QB1', 'QB2']], - 'measure': [['QB1'], ['QB2']], + 'measure': [['QB1'], ['QB2'], ['QB3']], 'barrier': [], }, } @@ -601,6 +607,19 @@ def dynamic_architecture_success(sample_dynamic_architecture) -> MockJsonRespons return MockJsonResponse(200, sample_dynamic_architecture.model_dump()) +@pytest.fixture() +def channel_properties_success() -> MockJsonResponse: + content = { + "QB1__flux.awg": {"fast_feedback_sources": []}, + "QB1__drive.awg": {"fast_feedback_sources": ["PL-1__readout"]}, + "QB2__drive.awg": {"fast_feedback_sources": ["PL-1__readout"]}, + "QB3__drive.awg": {"fast_feedback_sources": ["PL-2__readout"]}, + "PL-1__readout": {}, + "PL-2__readout": {}, + } + return MockJsonResponse(200, content) + + @pytest.fixture() def move_architecture_success(sample_move_architecture) -> MockJsonResponse: return MockJsonResponse(200, sample_move_architecture.model_dump()) diff --git a/tests/test_iqm_client.py b/tests/test_iqm_client.py index 451b0ad19..e5887bf15 100644 --- a/tests/test_iqm_client.py +++ b/tests/test_iqm_client.py @@ -26,6 +26,7 @@ from iqm.iqm_client import ( APIEndpoint, + APIVariant, ArchitectureRetrievalError, Circuit, CircuitCompilationOptions, @@ -588,6 +589,25 @@ def test_get_quantum_architecture( unstub() +def test_get_feedback_groups( + channel_properties_url, channel_properties_success, static_architecture_success +): + """Test retrieving the feedback groups.""" + base_url = "https://example.com" + when(requests).get(f"{base_url}/info/client-libraries", headers=ANY, timeout=ANY).thenReturn( + mock_supported_client_libraries_response() + ) + expect(requests, times=1).get(f"{base_url}/cocos/quantum-architecture", ...).thenReturn( + static_architecture_success) + iqm_client = IQMClient(base_url, api_variant=APIVariant.V2) + expect(requests, times=1).get(channel_properties_url, ...).thenReturn(channel_properties_success) + + assert iqm_client.get_feedback_groups() == (frozenset({"QB1", "QB2"}), frozenset({"QB3"})) + + verifyNoUnwantedInteractions() + unstub() + + def test_user_warning_is_emitted_when_warnings_in_response( sample_client, existing_job_url, job_result_with_warnings, existing_run_id ):