From 879133cbb11b9f80fdc0017ba83a12657bcac1f9 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Fri, 23 Feb 2024 12:53:59 -0800 Subject: [PATCH 01/98] feat: update log stream prefix for new jobs (#887) --- src/braket/aws/aws_quantum_job.py | 19 ++++++++--- .../cwl_insights_metrics_fetcher.py | 9 +++-- .../braket/aws/test_aws_quantum_job.py | 23 +++++++++++++ .../test_cwl_insights_metrics_fetcher.py | 34 +++++++++++++++++++ 4 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 711aeab1b..3c6dbe66e 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -286,6 +286,17 @@ def name(self) -> str: """str: The name of the quantum job.""" return self.metadata(use_cached_value=True).get("jobName") + @property + def _logs_prefix(self) -> str: + """str: the prefix for the job logs.""" + # jobs ARNs used to contain the job name and use a log prefix of `job-name` + # now job ARNs use a UUID and a log prefix of `job-name/UUID` + return ( + f"{self.name}" + if self.arn.endswith(self.name) + else f"{self.name}/{self.arn.split('/')[-1]}" + ) + def state(self, use_cached_value: bool = False) -> str: """The state of the quantum hybrid job. @@ -381,7 +392,6 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: ) log_group = AwsQuantumJob.LOG_GROUP - stream_prefix = f"{self.name}/" stream_names = [] # The list of log streams positions = {} # The current position in each stream, map of stream name -> position instance_count = self.metadata(use_cached_value=True)["instanceConfig"]["instanceCount"] @@ -395,7 +405,7 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: has_streams = logs.flush_log_streams( self._aws_session, log_group, - stream_prefix, + self._logs_prefix, stream_names, positions, instance_count, @@ -455,14 +465,15 @@ def metrics( """ fetcher = CwlInsightsMetricsFetcher(self._aws_session) metadata = self.metadata(True) - job_name = metadata["jobName"] job_start = None job_end = None if "startedAt" in metadata: job_start = int(metadata["startedAt"].timestamp()) if self.state() in AwsQuantumJob.TERMINAL_STATES and "endedAt" in metadata: job_end = int(math.ceil(metadata["endedAt"].timestamp())) - return fetcher.get_metrics_for_job(job_name, metric_type, statistic, job_start, job_end) + return fetcher.get_metrics_for_job( + self.name, metric_type, statistic, job_start, job_end, self._logs_prefix + ) def cancel(self) -> str: """Cancels the job. diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py index be4e72541..be4b426d8 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -137,6 +137,7 @@ def get_metrics_for_job( statistic: MetricStatistic = MetricStatistic.MAX, job_start_time: int | None = None, job_end_time: int | None = None, + stream_prefix: str | None = None, ) -> dict[str, list[Union[str, float, int]]]: """Synchronously retrieves all the algorithm metrics logged by a given Hybrid Job. @@ -150,6 +151,8 @@ def get_metrics_for_job( Default: 3 hours before job_end_time. job_end_time (int | None): If the hybrid job is complete, this should be the time at which the hybrid job finished. Default: current time. + stream_prefix (str | None): If a logs prefix is provided, it will be used instead + of the job name. Returns: dict[str, list[Union[str, float, int]]]: The metrics data, where the keys @@ -166,11 +169,11 @@ def get_metrics_for_job( query_end_time = job_end_time or int(time.time()) query_start_time = job_start_time or query_end_time - self.QUERY_DEFAULT_JOB_DURATION - # The hybrid job name needs to be unique to prevent jobs with similar names from being - # conflated. + stream_prefix = stream_prefix or job_name + query = ( f"fields @timestamp, @message " - f"| filter @logStream like /^{job_name}\\// " + f"| filter @logStream like /^{stream_prefix}\\// " f"| filter @message like /Metrics - /" ) diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index 7f9dc1a84..8df2118cf 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -1104,3 +1104,26 @@ def test_bad_device_arn_format(aws_session): with pytest.raises(ValueError, match=device_not_found): AwsQuantumJob._initialize_session(aws_session, "bad-arn-format", logger) + + +def test_logs_prefix(job_region, quantum_job_name, aws_session, generate_get_job_response): + aws_session.get_job.return_value = generate_get_job_response(jobName=quantum_job_name) + + # old jobs with the `arn:.../job-name` style ARN use `job-name/` as the logs prefix + name_arn = f"arn:aws:braket:{job_region}:875981177017:job/{quantum_job_name}" + quantum_job = AwsQuantumJob(name_arn, aws_session) + assert quantum_job._logs_prefix == f"{quantum_job_name}" + + # jobs with the `arn:.../uuid` style ARN use `job-name/uuid/` as the logs prefix + uuid_1 = "UUID-123456789" + uuid_2 = "UUID-987654321" + uuid_arn_1 = f"arn:aws:braket:{job_region}:875981177017:job/{uuid_1}" + uuid_job_1 = AwsQuantumJob(uuid_arn_1, aws_session) + uuid_arn_2 = f"arn:aws:braket:{job_region}:875981177017:job/{uuid_2}" + uuid_job_2 = AwsQuantumJob(uuid_arn_2, aws_session) + assert ( + uuid_job_1._logs_prefix + == f"{quantum_job_name}/{uuid_1}" + != uuid_job_2._logs_prefix + == f"{quantum_job_name}/{uuid_2}" + ) diff --git a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py index 17027cec4..5b3eb7e42 100644 --- a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py +++ b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py @@ -80,6 +80,40 @@ def test_get_all_metrics_complete_results(mock_add_metrics, mock_get_metrics, aw assert result == expected_result +@patch("braket.jobs.metrics_data.cwl_insights_metrics_fetcher.LogMetricsParser.get_parsed_metrics") +@patch("braket.jobs.metrics_data.cwl_insights_metrics_fetcher.LogMetricsParser.parse_log_message") +def test_get_all_metrics_complete_results_stream_prefix( + mock_add_metrics, mock_get_metrics, aws_session +): + logs_client_mock = Mock() + aws_session.logs_client = logs_client_mock + + logs_client_mock.start_query.return_value = {"queryId": "test"} + logs_client_mock.get_query_results.return_value = { + "status": "Complete", + "results": EXAMPLE_METRICS_LOG_LINES, + } + expected_result = {"Test": [0]} + mock_get_metrics.return_value = expected_result + + fetcher = CwlInsightsMetricsFetcher(aws_session) + + result = fetcher.get_metrics_for_job( + "test_job", job_start_time=1, job_end_time=2, stream_prefix="test_job/uuid" + ) + logs_client_mock.get_query_results.assert_called_with(queryId="test") + logs_client_mock.start_query.assert_called_with( + logGroupName="/aws/braket/jobs", + startTime=1, + endTime=2, + queryString="fields @timestamp, @message | filter @logStream like /^test_job/uuid\\//" + " | filter @message like /Metrics - /", + limit=10000, + ) + assert mock_add_metrics.call_args_list == EXPECTED_CALL_LIST + assert result == expected_result + + def test_get_all_metrics_timeout(aws_session): logs_client_mock = Mock() aws_session.logs_client = logs_client_mock From 09099c0927d5213cb763976855affaaed9615bb5 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Feb 2024 16:14:33 +0000 Subject: [PATCH 02/98] prepare release v1.71.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57e0980b3..9b4cdf903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.71.0 (2024-02-26) + +### Features + + * update log stream prefix for new jobs + ## v1.70.3 (2024-02-21) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 359a86456..86b26bf69 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.70.4.dev0" +__version__ = "1.71.0" From 3769c773246a721bd323fe4e04dac5c4be11d50c Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 26 Feb 2024 16:14:33 +0000 Subject: [PATCH 03/98] update development version to v1.71.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 86b26bf69..fde388ff2 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.71.0" +__version__ = "1.71.1.dev0" From ac47de89a721c72d3534c25cf0a152398bc21737 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 26 Feb 2024 11:20:33 -0800 Subject: [PATCH 04/98] feat: FreeParameterExpression division (#885) --- .../parametric/free_parameter_expression.py | 9 +++++++++ .../test_free_parameter_expression.py | 17 ++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index d3ad91259..cc339fe13 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -151,6 +151,15 @@ def __mul__(self, other: FreeParameterExpression): def __rmul__(self, other: FreeParameterExpression): return FreeParameterExpression(other * self.expression) + def __truediv__(self, other): + if issubclass(type(other), FreeParameterExpression): + return FreeParameterExpression(self.expression / other.expression) + else: + return FreeParameterExpression(self.expression / other) + + def __rtruediv__(self, other: FreeParameterExpression): + return FreeParameterExpression(other / self.expression) + def __pow__(self, other: FreeParameterExpression, modulo: float = None): if issubclass(type(other), FreeParameterExpression): return FreeParameterExpression(self.expression**other.expression) diff --git a/test/unit_tests/braket/parametric/test_free_parameter_expression.py b/test/unit_tests/braket/parametric/test_free_parameter_expression.py index 370d45083..1bf6818bf 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter_expression.py +++ b/test/unit_tests/braket/parametric/test_free_parameter_expression.py @@ -80,7 +80,6 @@ def test_commutativity(): def test_add(): add_expr = FreeParameter("theta") + FreeParameter("theta") expected = FreeParameterExpression(2 * FreeParameter("theta")) - assert add_expr == expected @@ -89,14 +88,12 @@ def test_sub(): expected = FreeParameterExpression(FreeParameter("theta")) - FreeParameterExpression( FreeParameter("alpha") ) - assert sub_expr == expected def test_r_sub(): r_sub_expr = 1 - FreeParameter("theta") expected = FreeParameterExpression(1 - FreeParameter("theta")) - assert r_sub_expr == expected @@ -106,6 +103,20 @@ def test_mul(): assert mul_expr == expected +def test_truediv(): + truediv_expr = FreeParameter("theta") / FreeParameter("alpha") + expected = FreeParameterExpression(FreeParameter("theta")) / FreeParameterExpression( + FreeParameter("alpha") + ) + assert truediv_expr == expected + + +def test_r_truediv(): + r_truediv_expr = 1 / FreeParameter("theta") + expected = FreeParameterExpression(1 / FreeParameter("theta")) + assert r_truediv_expr == expected + + def test_pow(): mul_expr = FreeParameter("theta") ** FreeParameter("alpha") * 2 expected = FreeParameterExpression(FreeParameter("theta") ** FreeParameter("alpha") * 2) From 9d711554dd18f371e634d77cfcaadea001b6669b Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 27 Feb 2024 16:13:22 +0000 Subject: [PATCH 05/98] prepare release v1.72.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b4cdf903..9f5d4b9c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.72.0 (2024-02-27) + +### Features + + * FreeParameterExpression division + ## v1.71.0 (2024-02-26) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fde388ff2..b0b886fa4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.71.1.dev0" +__version__ = "1.72.0" From 51360693af5f2b282ff37ca38ab766ce8fe64613 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 27 Feb 2024 16:13:22 +0000 Subject: [PATCH 06/98] update development version to v1.72.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b0b886fa4..02e831d5b 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.0" +__version__ = "1.72.1.dev0" From 0f9ff8d80a50203b632acf4c4a021a15cabf0141 Mon Sep 17 00:00:00 2001 From: Aaron Berdy Date: Tue, 27 Feb 2024 16:27:07 -0800 Subject: [PATCH 07/98] fix: escape slash in metrics prefix (#897) --- src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py | 2 +- .../jobs/metrics_data/test_cwl_insights_metrics_fetcher.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py index be4b426d8..b2d12fe36 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -169,7 +169,7 @@ def get_metrics_for_job( query_end_time = job_end_time or int(time.time()) query_start_time = job_start_time or query_end_time - self.QUERY_DEFAULT_JOB_DURATION - stream_prefix = stream_prefix or job_name + stream_prefix = (stream_prefix or job_name).replace("/", "\\/") query = ( f"fields @timestamp, @message " diff --git a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py index 5b3eb7e42..72f9c4db5 100644 --- a/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py +++ b/test/unit_tests/braket/jobs/metrics_data/test_cwl_insights_metrics_fetcher.py @@ -106,7 +106,7 @@ def test_get_all_metrics_complete_results_stream_prefix( logGroupName="/aws/braket/jobs", startTime=1, endTime=2, - queryString="fields @timestamp, @message | filter @logStream like /^test_job/uuid\\//" + queryString="fields @timestamp, @message | filter @logStream like /^test_job\\/uuid\\//" " | filter @message like /Metrics - /", limit=10000, ) From 732661dcbdfe0039cda932d4084b774390c9c238 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 28 Feb 2024 00:41:54 +0000 Subject: [PATCH 08/98] prepare release v1.72.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f5d4b9c0..22e4bfd5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.72.1 (2024-02-28) + +### Bug Fixes and Other Changes + + * escape slash in metrics prefix + ## v1.72.0 (2024-02-27) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 02e831d5b..799802700 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.1.dev0" +__version__ = "1.72.1" From f4378178155c5c31bb886bf9abfac181e9379ebb Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 28 Feb 2024 00:41:54 +0000 Subject: [PATCH 09/98] update development version to v1.72.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 799802700..6143a872f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.1" +__version__ = "1.72.2.dev0" From 0370766feca0be3a5a3128b44f65cf5132ca1099 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:09:27 -0800 Subject: [PATCH 10/98] infra: pin numpy to less than 2.x until we prepare for the migration (#890) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b336a2790..16f96bd31 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ "cloudpickle==2.2.1", "nest-asyncio", "networkx", - "numpy", + "numpy<2", "openpulse", "openqasm3", "sympy", From 431410066e9ba75f6b2b4a96fb8ad6ade2c61e5d Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Fri, 1 Mar 2024 18:09:01 -0500 Subject: [PATCH 11/98] fix: validate FreeParameter name (#895) * validate numerical free parameters * simplify validation * do not use _validate_name --------- Co-authored-by: Viraj Chaudhari <72896239+virajvchaudhari@users.noreply.github.com> Co-authored-by: Cody Wang --- src/braket/circuits/circuit.py | 5 +- src/braket/parametric/free_parameter.py | 11 ++++- .../parametric/free_parameter_expression.py | 2 +- src/braket/pulse/pulse_sequence.py | 5 +- .../braket/circuits/test_circuit.py | 46 ++++++++++++++++++- .../braket/parametric/test_free_parameter.py | 10 +++- 6 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index b3063c444..8b696f17f 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -19,6 +19,7 @@ import numpy as np import oqpy +from sympy import Expr from braket.circuits import compiler_directives from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram @@ -472,7 +473,9 @@ def add_instruction( if self._check_for_params(instruction): for param in instruction.operator.parameters: - if isinstance(param, FreeParameterExpression): + if isinstance(param, FreeParameterExpression) and isinstance( + param.expression, Expr + ): free_params = param.expression.free_symbols for parameter in free_params: self._parameters.add(FreeParameter(parameter.name)) diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 9d5826520..78fbe45b3 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -47,7 +47,7 @@ def __init__(self, name: str): >>> param1 = FreeParameter("theta") >>> param1 = FreeParameter("\u03B8") """ - self._name = Symbol(name) + self._set_name(name) super().__init__(expression=self._name) @property @@ -87,6 +87,15 @@ def __repr__(self) -> str: """ return self.name + def _set_name(self, name: str) -> None: + if not name: + raise ValueError("FreeParameter names must be non empty") + if not isinstance(name, str): + raise TypeError("FreeParameter names must be strings") + if not name[0].isalpha() and not name[0] == "_": + raise ValueError("FreeParameter names must start with a letter or an underscore") + self._name = Symbol(name) + def to_dict(self) -> dict: return { "__class__": self.__class__.__name__, diff --git a/src/braket/parametric/free_parameter_expression.py b/src/braket/parametric/free_parameter_expression.py index cc339fe13..fdd2f5474 100644 --- a/src/braket/parametric/free_parameter_expression.py +++ b/src/braket/parametric/free_parameter_expression.py @@ -107,7 +107,7 @@ def _parse_string_expression(self, expression: str) -> FreeParameterExpression: return self._eval_operation(ast.parse(expression, mode="eval").body) def _eval_operation(self, node: Any) -> FreeParameterExpression: - if isinstance(node, ast.Num): + if isinstance(node, ast.Constant): return FreeParameterExpression(node.n) elif isinstance(node, ast.Name): return FreeParameterExpression(sympy.Symbol(node.id)) diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index e6d78f11f..a788b38c0 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -20,6 +20,7 @@ from openpulse import ast from oqpy import BitVar, PhysicalQubits, Program +from sympy import Expr from braket.parametric.free_parameter import FreeParameter from braket.parametric.free_parameter_expression import FreeParameterExpression @@ -330,7 +331,9 @@ def _register_free_parameters( self, parameter: Union[float, FreeParameterExpression], ) -> None: - if isinstance(parameter, FreeParameterExpression): + if isinstance(parameter, FreeParameterExpression) and isinstance( + parameter.expression, Expr + ): for p in parameter.expression.free_symbols: self._free_parameters.add(FreeParameter(p.name)) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 3cf40fd89..91b19dba9 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -21,6 +21,7 @@ AsciiCircuitDiagram, Circuit, FreeParameter, + FreeParameterExpression, Gate, Instruction, Moments, @@ -745,6 +746,44 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): assert circ.to_ir() == expected +@pytest.mark.parametrize( + "circuit, serialization_properties, expected_ir", + [ + ( + Circuit() + .rx(0, 0.15) + .ry(1, FreeParameterExpression("0.3")) + .rx(2, 3 * FreeParameterExpression(1)), + OpenQASMSerializationProperties(QubitReferenceType.VIRTUAL), + OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[3] b;", + "qubit[3] q;", + "rx(0.15) q[0];", + "ry(0.3) q[1];", + "rx(3) q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + "b[2] = measure q[2];", + ] + ), + inputs={}, + ), + ), + ], +) +def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir): + assert ( + circuit.to_ir( + ir_type=IRType.OPENQASM, + serialization_properties=serialization_properties, + ) + == expected_ir + ) + + @pytest.mark.parametrize( "circuit, serialization_properties, expected_ir", [ @@ -1019,7 +1058,9 @@ def test_ir_non_empty_instructions_result_types_basis_rotation_instructions(): ), ], ) -def test_circuit_to_ir_openqasm(circuit, serialization_properties, expected_ir, gate_calibrations): +def test_circuit_to_ir_openqasm_with_gate_calibrations( + circuit, serialization_properties, expected_ir, gate_calibrations +): copy_of_gate_calibrations = gate_calibrations.copy() assert ( circuit.to_ir( @@ -1962,7 +2003,8 @@ def test_from_ir_inputs_updated(): source="\n".join( [ "OPENQASM 3.0;", - "input float theta;" "bit[1] b;", + "input float theta;", + "bit[1] b;", "qubit[1] q;", "rx(theta) q[0];", "rx(2*theta) q[0];", diff --git a/test/unit_tests/braket/parametric/test_free_parameter.py b/test/unit_tests/braket/parametric/test_free_parameter.py index 816bc0cee..b94c60a97 100644 --- a/test/unit_tests/braket/parametric/test_free_parameter.py +++ b/test/unit_tests/braket/parametric/test_free_parameter.py @@ -21,9 +21,15 @@ def free_parameter(): return FreeParameter("theta") -@pytest.mark.xfail(raises=TypeError) def test_bad_input(): - FreeParameter(6) + with pytest.raises(ValueError, match="FreeParameter names must be non empty"): + FreeParameter("") + with pytest.raises(TypeError, match="FreeParameter names must be strings"): + FreeParameter(6) + with pytest.raises( + ValueError, match="FreeParameter names must start with a letter or an underscore" + ): + FreeParameter(".2") def test_is_free_param(free_parameter): From f4d642ae00199ccdb17cfd4a3e1043069c9ea670 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Fri, 1 Mar 2024 21:38:36 -0800 Subject: [PATCH 12/98] test: Resolve integration test job ARNs becoming UUIDs (#900) --- test/integ_tests/conftest.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 2962c2048..2a56d8074 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -161,16 +161,22 @@ def job_failed_name(request): @pytest.fixture(scope="session", autouse=True) -def completed_quantum_job(aws_session, job_completed_name): - account = boto3.client("sts").get_caller_identity().get("Account") - region = aws_session.region - job = AwsQuantumJob(arn=f"arn:aws:braket:{region}:{account}:job/{job_completed_name}") - return job +def completed_quantum_job(job_completed_name): + job_arn = [ + job["jobArn"] + for job in boto3.client("braket").search_jobs(filters=[])["jobs"] + if job["jobName"] == job_completed_name + ][0] + + return AwsQuantumJob(arn=job_arn) @pytest.fixture(scope="session", autouse=True) -def failed_quantum_job(aws_session, job_failed_name): - account = boto3.client("sts").get_caller_identity().get("Account") - region = aws_session.region - job = AwsQuantumJob(arn=f"arn:aws:braket:{region}:{account}:job/{job_failed_name}") - return job +def failed_quantum_job(job_failed_name): + job_arn = [ + job["jobArn"] + for job in boto3.client("braket").search_jobs(filters=[])["jobs"] + if job["jobName"] == job_failed_name + ][0] + + return AwsQuantumJob(arn=job_arn) From 4b3f9906de850e6564155a46c846fa2745b828ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 01:41:05 -0800 Subject: [PATCH 13/98] infra: bump pypa/gh-action-pypi-publish from 1.8.11 to 1.8.12 (#901) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.11 to 1.8.12. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf...e53eb8b103ffcb59469888563dc324e3c8ba6f06) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index b12cde750..0ec7ff9b2 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf # release/v1 + uses: pypa/gh-action-pypi-publish@e53eb8b103ffcb59469888563dc324e3c8ba6f06 # release/v1 with: password: ${{ secrets.pypi_token }} From 8f2f2a2ed9159e0720e1484ed1e9e2292f518a4a Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 4 Mar 2024 16:13:59 +0000 Subject: [PATCH 14/98] prepare release v1.72.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e4bfd5a..62d625ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.72.2 (2024-03-04) + +### Bug Fixes and Other Changes + + * validate FreeParameter name + ## v1.72.1 (2024-02-28) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6143a872f..d6dc5d352 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.2.dev0" +__version__ = "1.72.2" From 4ff8f6f5816fe7aab344561fbecaf9732ebff14e Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 4 Mar 2024 16:13:59 +0000 Subject: [PATCH 15/98] update development version to v1.72.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d6dc5d352..b77e9d94c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.2" +__version__ = "1.72.3.dev0" From 4b9759903467ee7817671f6f109a35d6b93a60ce Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 4 Mar 2024 14:46:00 -0800 Subject: [PATCH 16/98] infra: Use Codecov token (#903) --- .github/workflows/python-package.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 74b49db9e..f216d2c19 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -37,4 +37,6 @@ jobs: tox -e unit-tests - name: Upload coverage report to Codecov uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # v3.1.5 + with: + token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From b4245ea1050cb9d2ee24b43c624a5ff7d606a96c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:53:33 -0800 Subject: [PATCH 17/98] infra: bump codecov/codecov-action from 3.1.5 to 4.0.1 (#868) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3.1.5 to 4.0.1. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0...e0b68c6749509c5f83f984dd99a76a1c1a231044) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f216d2c19..516f93c70 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # v3.1.5 + uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From c49176bc3427f747a35f3b23710cd24d6d8e6828 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:10:51 -0500 Subject: [PATCH 18/98] feature: update circuit drawing (#846) * replace control symbols * use box drawing characters * fix tests * switch to large b/w circle * first attempt to box symbols * fix single qubit circuit * rewrite first tests * first try to draw verbatim box * revamp verbatim box * update tests and skip outdated * modify last test * fix connections * fix linter * update more tests * update verbatix box tests * update last tests, left 6 xfail * remove margin * add connecting edges * code coverage * finish code coverage * decomplexify * add xfail test * move AsciiDiagram to BoxDrawingDiagram * keep ascii diagrams as legacy * add default_diagram_builder field * replace back circles by C/N * remove duplicate code * more simplification * add comment * add back build_diagram for readibilty * use a _build_box method * simplify _build_parameters * remove unnecessary code * mutualize create_output * add another xfail test * fix linters * fix misalignment * fix linters * cleanup * make _fill_symbol private * clean an branching condition * draw swap gates with x * remove commented tests * clean implementation * keep AsciiCircuitDiagram as default for now * reorganize class structure * fix docstring * make class-specific method explicit * fix linters * fix typos * remove forgotten argument * use a utilities class * do not use a TextCircuitDiagramUtilities * rename methods * rename to text_circuit_diagram_utils * use cls var * first changes according to PR feedback * Attempt at simplification (#898) * add docstrings and rename box_drawing_circuit_diagram after merge * standardize type hints of _draw_symbol * small changes according to PR feedback. * change a staticmethod to a classmethod --------- Co-authored-by: Cody Wang Co-authored-by: Milan <30416311+krneta@users.noreply.github.com> --- src/braket/circuits/__init__.py | 7 +- src/braket/circuits/ascii_circuit_diagram.py | 403 +------ src/braket/circuits/circuit.py | 6 +- .../ascii_circuit_diagram.py | 195 ++++ .../text_circuit_diagram.py | 265 +++++ .../text_circuit_diagram_utils.py | 197 ++++ .../unicode_circuit_diagram.py | 283 +++++ .../circuits/test_ascii_circuit_diagram.py | 8 +- .../braket/circuits/test_circuit.py | 4 +- .../circuits/test_unicode_circuit_diagram.py | 1021 +++++++++++++++++ 10 files changed, 1981 insertions(+), 408 deletions(-) create mode 100644 src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py create mode 100644 src/braket/circuits/text_diagram_builders/text_circuit_diagram.py create mode 100644 src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py create mode 100644 src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py create mode 100644 test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index d2788746c..a5fb52980 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -20,7 +20,6 @@ result_types, ) from braket.circuits.angled_gate import AngledGate, DoubleAngledGate # noqa: F401 -from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram # noqa: F401 from braket.circuits.circuit import Circuit # noqa: F401 from braket.circuits.circuit_diagram import CircuitDiagram # noqa: F401 from braket.circuits.compiler_directive import CompilerDirective # noqa: F401 @@ -38,3 +37,9 @@ from braket.circuits.qubit import Qubit, QubitInput # noqa: F401 from braket.circuits.qubit_set import QubitSet, QubitSetInput # noqa: F401 from braket.circuits.result_type import ObservableResultType, ResultType # noqa: F401 +from braket.circuits.text_diagram_builders.ascii_circuit_diagram import ( # noqa: F401 + AsciiCircuitDiagram, +) +from braket.circuits.text_diagram_builders.unicode_circuit_diagram import ( # noqa: F401 + UnicodeCircuitDiagram, +) diff --git a/src/braket/circuits/ascii_circuit_diagram.py b/src/braket/circuits/ascii_circuit_diagram.py index c255377b5..accbce161 100644 --- a/src/braket/circuits/ascii_circuit_diagram.py +++ b/src/braket/circuits/ascii_circuit_diagram.py @@ -11,401 +11,8 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from __future__ import annotations - -from functools import reduce -from typing import Union - -import braket.circuits.circuit as cir -from braket.circuits.circuit_diagram import CircuitDiagram -from braket.circuits.compiler_directive import CompilerDirective -from braket.circuits.gate import Gate -from braket.circuits.instruction import Instruction -from braket.circuits.moments import MomentType -from braket.circuits.noise import Noise -from braket.circuits.result_type import ResultType -from braket.registers.qubit import Qubit -from braket.registers.qubit_set import QubitSet - - -class AsciiCircuitDiagram(CircuitDiagram): - """Builds ASCII string circuit diagrams.""" - - @staticmethod - def build_diagram(circuit: cir.Circuit) -> str: - """Build an ASCII string circuit diagram. - - Args: - circuit (cir.Circuit): Circuit for which to build a diagram. - - Returns: - str: ASCII string circuit diagram. - """ - if not circuit.instructions: - return "" - - if all(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): - return f"Global phase: {circuit.global_phase}" - - circuit_qubits = circuit.qubits - circuit_qubits.sort() - - y_axis_str, global_phase = AsciiCircuitDiagram._prepare_diagram_vars( - circuit, circuit_qubits - ) - - time_slices = circuit.moments.time_slices() - column_strs = [] - - # Moment columns - for time, instructions in time_slices.items(): - global_phase = AsciiCircuitDiagram._compute_moment_global_phase( - global_phase, instructions - ) - moment_str = AsciiCircuitDiagram._ascii_diagram_column_set( - str(time), circuit_qubits, instructions, global_phase - ) - column_strs.append(moment_str) - - # Result type columns - additional_result_types, target_result_types = AsciiCircuitDiagram._categorize_result_types( - circuit.result_types - ) - if target_result_types: - column_strs.append( - AsciiCircuitDiagram._ascii_diagram_column_set( - "Result Types", circuit_qubits, target_result_types, global_phase - ) - ) - - # Unite strings - lines = y_axis_str.split("\n") - for col_str in column_strs: - for i, line_in_col in enumerate(col_str.split("\n")): - lines[i] += line_in_col - - # Time on top and bottom - lines.append(lines[0]) - - if global_phase: - lines.append(f"\nGlobal phase: {global_phase}") - - # Additional result types line on bottom - if additional_result_types: - lines.append(f"\nAdditional result types: {', '.join(additional_result_types)}") - - # A list of parameters in the circuit to the currently assigned values. - if circuit.parameters: - lines.append( - "\nUnassigned parameters: " - f"{sorted(circuit.parameters, key=lambda param: param.name)}." - ) - - return "\n".join(lines) - - @staticmethod - def _prepare_diagram_vars( - circuit: cir.Circuit, circuit_qubits: QubitSet - ) -> tuple[str, float | None]: - # Y Axis Column - y_axis_width = len(str(int(max(circuit_qubits)))) - y_axis_str = "{0:{width}} : |\n".format("T", width=y_axis_width + 1) - - global_phase = None - if any(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): - y_axis_str += "{0:{width}} : |\n".format("GP", width=y_axis_width) - global_phase = 0 - - for qubit in circuit_qubits: - y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) - y_axis_str += "q{0:{width}} : -\n".format(str(int(qubit)), width=y_axis_width) - - return y_axis_str, global_phase - - @staticmethod - def _compute_moment_global_phase( - global_phase: float | None, items: list[Instruction] - ) -> float | None: - """Compute the integrated phase at a certain moment. - - Args: - global_phase (float | None): The integrated phase up to the computed moment - items (list[Instruction]): list of instructions - - Returns: - float | None: The updated integrated phase. - """ - moment_phase = 0 - for item in items: - if ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ): - moment_phase += item.operator.angle - return global_phase + moment_phase if global_phase is not None else None - - @staticmethod - def _ascii_group_items( - circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], - ) -> list[tuple[QubitSet, list[Instruction]]]: - """Group instructions in a moment for ASCII diagram - - Args: - circuit_qubits (QubitSet): set of qubits in circuit - items (list[Union[Instruction, ResultType]]): list of instructions or result types - - Returns: - list[tuple[QubitSet, list[Instruction]]]: list of grouped instructions or result types. - """ - groupings = [] - for item in items: - # Can only print Gate and Noise operators for instructions at the moment - if isinstance(item, Instruction) and not isinstance( - item.operator, (Gate, Noise, CompilerDirective) - ): - continue - - # As a zero-qubit gate, GPhase can be grouped with anything. We set qubit_range - # to an empty list and we just add it to the first group below. - if ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ): - qubit_range = QubitSet() - elif (isinstance(item, ResultType) and not item.target) or ( - isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) - ): - qubit_range = circuit_qubits - else: - if isinstance(item.target, list): - target = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) - else: - target = item.target - control = getattr(item, "control", QubitSet()) - target_and_control = target.union(control) - qubit_range = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) - - found_grouping = False - for group in groupings: - qubits_added = group[0] - instr_group = group[1] - # Take into account overlapping multi-qubit gates - if not qubits_added.intersection(set(qubit_range)): - instr_group.append(item) - qubits_added.update(qubit_range) - found_grouping = True - break - - if not found_grouping: - groupings.append((qubit_range, [item])) - - return groupings - - @staticmethod - def _categorize_result_types( - result_types: list[ResultType], - ) -> tuple[list[str], list[ResultType]]: - """Categorize result types into result types with target and those without. - - Args: - result_types (list[ResultType]): list of result types - - Returns: - tuple[list[str], list[ResultType]]: first element is a list of result types - without `target` attribute; second element is a list of result types with - `target` attribute - """ - additional_result_types = [] - target_result_types = [] - for result_type in result_types: - if hasattr(result_type, "target"): - target_result_types.append(result_type) - else: - additional_result_types.extend(result_type.ascii_symbols) - return additional_result_types, target_result_types - - @staticmethod - def _ascii_diagram_column_set( - col_title: str, - circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], - global_phase: float | None, - ) -> str: - """Return a set of columns in the ASCII string diagram of the circuit for a list of items. - - Args: - col_title (str): title of column set - circuit_qubits (QubitSet): qubits in circuit - items (list[Union[Instruction, ResultType]]): list of instructions or result types - global_phase (float | None): the integrated global phase up to this set - - Returns: - str: An ASCII string diagram for the column set. - """ - # Group items to separate out overlapping multi-qubit items - groupings = AsciiCircuitDiagram._ascii_group_items(circuit_qubits, items) - - column_strs = [ - AsciiCircuitDiagram._ascii_diagram_column(circuit_qubits, grouping[1], global_phase) - for grouping in groupings - ] - - # Unite column strings - lines = column_strs[0].split("\n") - for column_str in column_strs[1:]: - for i, moment_line in enumerate(column_str.split("\n")): - lines[i] += moment_line - - # Adjust for column title width - col_title_width = len(col_title) - symbols_width = len(lines[0]) - 1 - if symbols_width < col_title_width: - diff = col_title_width - symbols_width - for i in range(len(lines) - 1): - if lines[i].endswith("-"): - lines[i] += "-" * diff - else: - lines[i] += " " - - first_line = "{:^{width}}|\n".format(col_title, width=len(lines[0]) - 1) - - return first_line + "\n".join(lines) - - @staticmethod - def _ascii_diagram_column( - circuit_qubits: QubitSet, - items: list[Union[Instruction, ResultType]], - global_phase: float | None = None, - ) -> str: - """Return a column in the ASCII string diagram of the circuit for a given list of items. - - Args: - circuit_qubits (QubitSet): qubits in circuit - items (list[Union[Instruction, ResultType]]): list of instructions or result types - global_phase (float | None): the integrated global phase up to this column - - Returns: - str: an ASCII string diagram for the specified moment in time for a column. - """ - symbols = {qubit: "-" for qubit in circuit_qubits} - margins = {qubit: " " for qubit in circuit_qubits} - - for item in items: - if isinstance(item, ResultType) and not item.target: - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = target_qubits.union(control_qubits) - qubits = circuit_qubits - ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) - elif isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective): - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = target_qubits.union(control_qubits) - qubits = circuit_qubits - ascii_symbol = item.ascii_symbols[0] - marker = "*" * len(ascii_symbol) - num_after = len(circuit_qubits) - 1 - after = ["|"] * (num_after - 1) + ([marker] if num_after else []) - ascii_symbols = [ascii_symbol, *after] - elif ( - isinstance(item, Instruction) - and isinstance(item.operator, Gate) - and item.operator.name == "GPhase" - ): - target_qubits = circuit_qubits - control_qubits = QubitSet() - target_and_control = QubitSet() - qubits = circuit_qubits - ascii_symbols = "-" * len(circuit_qubits) - else: - if isinstance(item.target, list): - target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) - else: - target_qubits = item.target - control_qubits = getattr(item, "control", QubitSet()) - map_control_qubit_states = AsciiCircuitDiagram._build_map_control_qubits( - item, control_qubits - ) - - target_and_control = target_qubits.union(control_qubits) - qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) - - ascii_symbols = item.ascii_symbols - - for qubit in qubits: - # Determine if the qubit is part of the item or in the middle of a - # multi qubit item. - if qubit in target_qubits: - item_qubit_index = [ - index for index, q in enumerate(target_qubits) if q == qubit - ][0] - power_string = ( - f"^{power}" - if ( - (power := getattr(item, "power", 1)) != 1 - # this has the limitation of not printing the power - # when a user has a gate genuinely named C, but - # is necessary to enable proper printing of custom - # gates with built-in control qubits - and ascii_symbols[item_qubit_index] != "C" - ) - else "" - ) - symbols[qubit] = ( - f"({ascii_symbols[item_qubit_index]}{power_string})" - if power_string - else ascii_symbols[item_qubit_index] - ) - elif qubit in control_qubits: - symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" - else: - symbols[qubit] = "|" - - # Set the margin to be a connector if not on the first qubit - if target_and_control and qubit != min(target_and_control): - margins[qubit] = "|" - - output = AsciiCircuitDiagram._create_output(symbols, margins, circuit_qubits, global_phase) - return output - - @staticmethod - def _create_output( - symbols: dict[Qubit, str], - margins: dict[Qubit, str], - qubits: QubitSet, - global_phase: float | None, - ) -> str: - symbols_width = max([len(symbol) for symbol in symbols.values()]) - output = "" - - if global_phase is not None: - global_phase_str = ( - f"{global_phase:.2f}" if isinstance(global_phase, float) else str(global_phase) - ) - symbols_width = max([symbols_width, len(global_phase_str)]) - output += "{0:{fill}{align}{width}}|\n".format( - global_phase_str, - fill=" ", - align="^", - width=symbols_width, - ) - - for qubit in qubits: - output += "{0:{width}}\n".format(margins[qubit], width=symbols_width + 1) - output += "{0:{fill}{align}{width}}\n".format( - symbols[qubit], fill="-", align="<", width=symbols_width + 1 - ) - return output - - @staticmethod - def _build_map_control_qubits(item: Instruction, control_qubits: QubitSet) -> dict(Qubit, int): - control_state = getattr(item, "control_state", None) - if control_state is not None: - map_control_qubit_states = dict(zip(control_qubits, control_state)) - else: - map_control_qubit_states = {qubit: 1 for qubit in control_qubits} - - return map_control_qubit_states +# Moving ascii_circuit_diagram.py into the text_diagram_builders folder in order +# to group all classes that print circuits in a text format. +from braket.circuits.text_diagram_builders.ascii_circuit_diagram import ( # noqa: F401 + AsciiCircuitDiagram, +) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 8b696f17f..36f0e68fb 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -22,7 +22,6 @@ from sympy import Expr from braket.circuits import compiler_directives -from braket.circuits.ascii_circuit_diagram import AsciiCircuitDiagram from braket.circuits.free_parameter import FreeParameter from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate @@ -51,6 +50,7 @@ QubitReferenceType, SerializationProperties, ) +from braket.circuits.text_diagram_builders.unicode_circuit_diagram import UnicodeCircuitDiagram from braket.circuits.unitary_calculation import calculate_unitary_big_endian from braket.default_simulator.openqasm.interpreter import Interpreter from braket.ir.jaqcd import Program as JaqcdProgram @@ -1086,7 +1086,7 @@ def adjoint(self) -> Circuit: circ.add_result_type(result_type) return circ - def diagram(self, circuit_diagram_class: type = AsciiCircuitDiagram) -> str: + def diagram(self, circuit_diagram_class: type = UnicodeCircuitDiagram) -> str: """Get a diagram for the current circuit. Args: @@ -1498,7 +1498,7 @@ def __repr__(self) -> str: ) def __str__(self): - return self.diagram(AsciiCircuitDiagram) + return self.diagram() def __eq__(self, other: Circuit): if isinstance(other, Circuit): diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py new file mode 100644 index 000000000..3106afc47 --- /dev/null +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -0,0 +1,195 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from __future__ import annotations + +from functools import reduce +from typing import Literal, Union + +import braket.circuits.circuit as cir +from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.result_type import ResultType +from braket.circuits.text_diagram_builders.text_circuit_diagram import TextCircuitDiagram +from braket.registers.qubit_set import QubitSet + + +class AsciiCircuitDiagram(TextCircuitDiagram): + """Builds ASCII string circuit diagrams.""" + + @staticmethod + def build_diagram(circuit: cir.Circuit) -> str: + """Build a text circuit diagram. + + Args: + circuit (Circuit): Circuit for which to build a diagram. + + Returns: + str: string circuit diagram. + """ + return AsciiCircuitDiagram._build(circuit) + + @classmethod + def _vertical_delimiter(cls) -> str: + """Character that connects qubits of multi-qubit gates.""" + return "|" + + @classmethod + def _qubit_line_character(cls) -> str: + """Character used for the qubit line.""" + return "-" + + @classmethod + def _box_pad(cls) -> int: + """number of blank space characters around the gate name.""" + return 0 + + @classmethod + def _qubit_line_spacing_above(cls) -> int: + """number of empty lines above the qubit line.""" + return 1 + + @classmethod + def _qubit_line_spacing_below(cls) -> int: + """number of empty lines below the qubit line.""" + return 0 + + @classmethod + def _duplicate_time_at_bottom(cls, lines: str) -> None: + # duplicate times after an empty line + lines.append(lines[0]) + + @classmethod + def _create_diagram_column( + cls, + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], + global_phase: float | None = None, + ) -> str: + """Return a column in the ASCII string diagram of the circuit for a given list of items. + + Args: + circuit_qubits (QubitSet): qubits in circuit + items (list[Union[Instruction, ResultType]]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this column + + Returns: + str: an ASCII string diagram for the specified moment in time for a column. + """ + symbols = {qubit: cls._qubit_line_character() for qubit in circuit_qubits} + connections = {qubit: "none" for qubit in circuit_qubits} + + for item in items: + if isinstance(item, ResultType) and not item.target: + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = target_qubits.union(control_qubits) + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(circuit_qubits) + elif isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective): + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = target_qubits.union(control_qubits) + qubits = circuit_qubits + ascii_symbol = item.ascii_symbols[0] + marker = "*" * len(ascii_symbol) + num_after = len(circuit_qubits) - 1 + after = ["|"] * (num_after - 1) + ([marker] if num_after else []) + ascii_symbols = [ascii_symbol, *after] + elif ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + target_and_control = QubitSet() + qubits = circuit_qubits + ascii_symbols = cls._qubit_line_character() * len(circuit_qubits) + else: + if isinstance(item.target, list): + target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target_qubits = item.target + control_qubits = getattr(item, "control", QubitSet()) + control_state = getattr(item, "control_state", "1" * len(control_qubits)) + map_control_qubit_states = { + qubit: state for qubit, state in zip(control_qubits, control_state) + } + + target_and_control = target_qubits.union(control_qubits) + qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + + ascii_symbols = item.ascii_symbols + + for qubit in qubits: + # Determine if the qubit is part of the item or in the middle of a + # multi qubit item. + if qubit in target_qubits: + item_qubit_index = [ + index for index, q in enumerate(target_qubits) if q == qubit + ][0] + power_string = ( + f"^{power}" + if ( + (power := getattr(item, "power", 1)) != 1 + # this has the limitation of not printing the power + # when a user has a gate genuinely named C, but + # is necessary to enable proper printing of custom + # gates with built-in control qubits + and ascii_symbols[item_qubit_index] != "C" + ) + else "" + ) + symbols[qubit] = ( + f"({ascii_symbols[item_qubit_index]}{power_string})" + if power_string + else ascii_symbols[item_qubit_index] + ) + elif qubit in control_qubits: + symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" + else: + symbols[qubit] = "|" + + # Set the margin to be a connector if not on the first qubit + if target_and_control and qubit != min(target_and_control): + connections[qubit] = "above" + + output = cls._create_output(symbols, connections, circuit_qubits, global_phase) + return output + + # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] + # flake8: noqa: BCS005 + @classmethod + def _draw_symbol( + cls, symbol: str, symbols_width: int, connection: Literal["above", "below", "both", "none"] + ) -> str: + """Create a string representing the symbol. + + Args: + symbol (str): the gate name + symbols_width (int): size of the expected output. The ouput will be filled with + cls._qubit_line_character() if needed. + connection (Literal["above", "below", "both", "none"]): character indicating + if the gate also involve a qubit with a lower index. + + Returns: + str: a string representing the symbol. + """ + connection_char = cls._vertical_delimiter() if connection in ["above"] else " " + output = "{0:{width}}\n".format(connection_char, width=symbols_width + 1) + output += "{0:{fill}{align}{width}}\n".format( + symbol, fill=cls._qubit_line_character(), align="<", width=symbols_width + 1 + ) + return output diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py new file mode 100644 index 000000000..c8bfa2650 --- /dev/null +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -0,0 +1,265 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Literal, Union + +import braket.circuits.circuit as cir +from braket.circuits.circuit_diagram import CircuitDiagram +from braket.circuits.instruction import Instruction +from braket.circuits.moments import MomentType +from braket.circuits.result_type import ResultType +from braket.circuits.text_diagram_builders.text_circuit_diagram_utils import ( + _add_footers, + _categorize_result_types, + _compute_moment_global_phase, + _group_items, + _prepare_qubit_identifier_column, + _unite_strings, +) +from braket.registers.qubit import Qubit +from braket.registers.qubit_set import QubitSet + + +class TextCircuitDiagram(CircuitDiagram, ABC): + """Abstract base class for text circuit diagrams.""" + + @classmethod + @abstractmethod + def _vertical_delimiter(cls) -> str: + """Character that connects qubits of multi-qubit gates.""" + + @classmethod + @abstractmethod + def _qubit_line_character(cls) -> str: + """Character used for the qubit line.""" + + @classmethod + @abstractmethod + def _box_pad(cls) -> int: + """number of blank space characters around the gate name.""" + + @classmethod + @abstractmethod + def _qubit_line_spacing_above(cls) -> int: + """number of empty lines above the qubit line.""" + + @classmethod + @abstractmethod + def _qubit_line_spacing_below(cls) -> int: + """number of empty lines below the qubit line.""" + + @classmethod + @abstractmethod + def _create_diagram_column( + cls, + circuit_qubits: QubitSet, + items: list[Instruction | ResultType], + global_phase: float | None = None, + ) -> str: + """Return a column in the string diagram of the circuit for a given list of items. + + Args: + circuit_qubits (QubitSet): qubits in circuit + items (list[Instruction | ResultType]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this column + + Returns: + str: a string diagram for the specified moment in time for a column. + """ + + # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] + # flake8: noqa: BCS005 + @classmethod + @abstractmethod + def _draw_symbol( + cls, + symbol: str, + symbols_width: int, + connection: Literal["above", "below", "both", "none"], + ) -> str: + """Create a string representing the symbol inside a box. + + Args: + symbol (str): the gate name + symbols_width (int): size of the expected output. The ouput will be filled with + cls._qubit_line_character() if needed. + connection (Literal["above", "below", "both", "none"]): specifies if a connection + will be drawn above and/or below the box. + + Returns: + str: a string representing the symbol. + """ + + @classmethod + def _build(cls, circuit: cir.Circuit) -> str: + """Build a text circuit diagram. + + The procedure follows as: + 1. Prepare the first column composed of the qubit identifiers + 2. Construct the circuit as a list of columns by looping through the + time slices. A column is a string with rows separated via '\n' + a. compute the instantaneous global phase + b. create the column corresponding to the current moment + 3. Add result types at the end of the circuit + 4. Join the columns to get a list of qubit lines + 5. Add a list of optional parameters: + a. the total global phase + b. results types that do not have any target such as statevector + c. the list of unassigned parameters + + Args: + circuit (Circuit): Circuit for which to build a diagram. + + Returns: + str: string circuit diagram. + """ + if not circuit.instructions: + return "" + + if all(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): + return f"Global phase: {circuit.global_phase}" + + circuit_qubits = circuit.qubits + circuit_qubits.sort() + + y_axis_str, global_phase = _prepare_qubit_identifier_column( + circuit, + circuit_qubits, + cls._vertical_delimiter(), + cls._qubit_line_character(), + cls._qubit_line_spacing_above(), + cls._qubit_line_spacing_below(), + ) + + column_strs = [] + + global_phase, additional_result_types = cls._build_columns( + circuit, circuit_qubits, global_phase, column_strs + ) + + # Unite strings + lines = _unite_strings(y_axis_str, column_strs) + cls._duplicate_time_at_bottom(lines) + + return _add_footers(lines, circuit, global_phase, additional_result_types) + + @classmethod + def _build_columns( + cls, + circuit: cir.Circuit, + circuit_qubits: QubitSet, + global_phase: float | None, + column_strs: list, + ) -> tuple[float | None, list[str]]: + time_slices = circuit.moments.time_slices() + + # Moment columns + for time, instructions in time_slices.items(): + global_phase = _compute_moment_global_phase(global_phase, instructions) + moment_str = cls._create_diagram_column_set( + str(time), circuit_qubits, instructions, global_phase + ) + column_strs.append(moment_str) + + # Result type columns + additional_result_types, target_result_types = _categorize_result_types( + circuit.result_types + ) + if target_result_types: + column_strs.append( + cls._create_diagram_column_set( + "Result Types", circuit_qubits, target_result_types, global_phase + ) + ) + return global_phase, additional_result_types + + @classmethod + def _create_diagram_column_set( + cls, + col_title: str, + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], + global_phase: float | None, + ) -> str: + """Return a set of columns in the string diagram of the circuit for a list of items. + + Args: + col_title (str): title of column set + circuit_qubits (QubitSet): qubits in circuit + items (list[Union[Instruction, ResultType]]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this set + + Returns: + str: A string diagram for the column set. + """ + + # Group items to separate out overlapping multi-qubit items + groupings = _group_items(circuit_qubits, items) + + column_strs = [ + cls._create_diagram_column(circuit_qubits, grouping[1], global_phase) + for grouping in groupings + ] + + # Unite column strings + lines = _unite_strings(column_strs[0], column_strs[1:]) + + # Adjust for column title width + col_title_width = len(col_title) + symbols_width = len(lines[0]) - 1 + if symbols_width < col_title_width: + diff = col_title_width - symbols_width + for i in range(len(lines) - 1): + if lines[i].endswith(cls._qubit_line_character()): + lines[i] += cls._qubit_line_character() * diff + else: + lines[i] += " " + + first_line = "{:^{width}}{vdelim}\n".format( + col_title, width=len(lines[0]) - 1, vdelim=cls._vertical_delimiter() + ) + + return first_line + "\n".join(lines) + + @classmethod + def _create_output( + cls, + symbols: dict[Qubit, str], + margins: dict[Qubit, str], + qubits: QubitSet, + global_phase: float | None, + ) -> str: + """Creates the ouput for a single column: + a. If there was one or more gphase gate, create a first line with the total global + phase shift ending with the _vertical_delimiter() class attribute, e.g. 0.14| + b. for each qubit, append the text representation produces by cls._draw_symbol + + Args: + symbols (dict[Qubit, str]): dictionary of the gate name for each qubit + margins (dict[Qubit, str]): map of the qubit interconnections. Specific to the + `_draw_symbol` classmethod. + qubits (QubitSet): set of the circuit qubits + global_phase (float | None): total global phase shift added during the moment + + Returns: + str: a string representing a diagram column. + """ + symbols_width = max([len(symbol) for symbol in symbols.values()]) + cls._box_pad() + output = "" + + if global_phase is not None: + global_phase_str = ( + f"{global_phase:.2f}" if isinstance(global_phase, float) else str(global_phase) + ) + symbols_width = max([symbols_width, len(global_phase_str)]) + output += "{0:{fill}{align}{width}}{vdelim}\n".format( + global_phase_str, + fill=" ", + align="^", + width=symbols_width, + vdelim=cls._vertical_delimiter(), + ) + + for qubit in qubits: + output += cls._draw_symbol(symbols[qubit], symbols_width, margins[qubit]) + return output diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py new file mode 100644 index 000000000..9b85c30e1 --- /dev/null +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -0,0 +1,197 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from __future__ import annotations + +from functools import reduce +from typing import Union + +import braket.circuits.circuit as cir +from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.moments import MomentType +from braket.circuits.noise import Noise +from braket.circuits.result_type import ResultType +from braket.registers.qubit_set import QubitSet + + +def _add_footers( + lines: list, + circuit: cir.Circuit, + global_phase: float | None, + additional_result_types: list[str], +) -> str: + if global_phase: + lines.append(f"\nGlobal phase: {global_phase}") + + # Additional result types line on bottom + if additional_result_types: + lines.append(f"\nAdditional result types: {', '.join(additional_result_types)}") + + # A list of parameters in the circuit to the currently assigned values. + if circuit.parameters: + lines.append( + "\nUnassigned parameters: " + f"{sorted(circuit.parameters, key=lambda param: param.name)}." + ) + + return "\n".join(lines) + + +def _prepare_qubit_identifier_column( + circuit: cir.Circuit, + circuit_qubits: QubitSet, + vdelim: str, + qubit_line_char: str, + line_spacing_before: int, + line_spacing_after: int, +) -> tuple[str, float | None]: + # Y Axis Column + y_axis_width = len(str(int(max(circuit_qubits)))) + y_axis_str = "{0:{width}} : {vdelim}\n".format("T", width=y_axis_width + 1, vdelim=vdelim) + + global_phase = None + if any(m.moment_type == MomentType.GLOBAL_PHASE for m in circuit._moments): + y_axis_str += "{0:{width}} : {vdelim}\n".format("GP", width=y_axis_width, vdelim=vdelim) + global_phase = 0 + + for qubit in circuit_qubits: + for _ in range(line_spacing_before): + y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) + + y_axis_str += "q{0:{width}} : {qubit_line_char}\n".format( + str(int(qubit)), + width=y_axis_width, + qubit_line_char=qubit_line_char, + ) + + for _ in range(line_spacing_after): + y_axis_str += "{0:{width}}\n".format(" ", width=y_axis_width + 5) + return y_axis_str, global_phase + + +def _unite_strings(first_column: str, column_strs: list[str]) -> list: + lines = first_column.split("\n") + for col_str in column_strs: + for i, line_in_col in enumerate(col_str.split("\n")): + lines[i] += line_in_col + return lines + + +def _compute_moment_global_phase( + global_phase: float | None, items: list[Instruction] +) -> float | None: + """ + Compute the integrated phase at a certain moment. + + Args: + global_phase (float | None): The integrated phase up to the computed moment + items (list[Instruction]): list of instructions + + Returns: + float | None: The updated integrated phase. + """ + moment_phase = 0 + for item in items: + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + moment_phase += item.operator.angle + return global_phase + moment_phase if global_phase is not None else None + + +def _group_items( + circuit_qubits: QubitSet, + items: list[Union[Instruction, ResultType]], +) -> list[tuple[QubitSet, list[Instruction]]]: + """ + Group instructions in a moment + + Args: + circuit_qubits (QubitSet): set of qubits in circuit + items (list[Union[Instruction, ResultType]]): list of instructions or result types + + Returns: + list[tuple[QubitSet, list[Instruction]]]: list of grouped instructions or result types. + """ + groupings = [] + for item in items: + # Can only print Gate and Noise operators for instructions at the moment + if isinstance(item, Instruction) and not isinstance( + item.operator, (Gate, Noise, CompilerDirective) + ): + continue + + # As a zero-qubit gate, GPhase can be grouped with anything. We set qubit_range + # to an empty list and we just add it to the first group below. + if ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + qubit_range = QubitSet() + elif (isinstance(item, ResultType) and not item.target) or ( + isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) + ): + qubit_range = circuit_qubits + else: + if isinstance(item.target, list): + target = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target = item.target + control = getattr(item, "control", QubitSet()) + target_and_control = target.union(control) + qubit_range = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + + found_grouping = False + for group in groupings: + qubits_added = group[0] + instr_group = group[1] + # Take into account overlapping multi-qubit gates + if not qubits_added.intersection(set(qubit_range)): + instr_group.append(item) + qubits_added.update(qubit_range) + found_grouping = True + break + + if not found_grouping: + groupings.append((qubit_range, [item])) + + return groupings + + +def _categorize_result_types( + result_types: list[ResultType], +) -> tuple[list[str], list[ResultType]]: + """ + Categorize result types into result types with target and those without. + + Args: + result_types (list[ResultType]): list of result types + + Returns: + tuple[list[str], list[ResultType]]: first element is a list of result types + without `target` attribute; second element is a list of result types with + `target` attribute + """ + additional_result_types = [] + target_result_types = [] + for result_type in result_types: + if hasattr(result_type, "target"): + target_result_types.append(result_type) + else: + additional_result_types.extend(result_type.ascii_symbols) + return additional_result_types, target_result_types diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py new file mode 100644 index 000000000..9e1779cb7 --- /dev/null +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -0,0 +1,283 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from __future__ import annotations + +from functools import reduce +from typing import Literal + +import braket.circuits.circuit as cir +from braket.circuits.compiler_directive import CompilerDirective +from braket.circuits.gate import Gate +from braket.circuits.instruction import Instruction +from braket.circuits.result_type import ResultType +from braket.circuits.text_diagram_builders.text_circuit_diagram import TextCircuitDiagram +from braket.registers.qubit import Qubit +from braket.registers.qubit_set import QubitSet + + +class UnicodeCircuitDiagram(TextCircuitDiagram): + """Builds string circuit diagrams using box-drawing characters.""" + + @staticmethod + def build_diagram(circuit: cir.Circuit) -> str: + """Build a text circuit diagram. + + Args: + circuit (Circuit): Circuit for which to build a diagram. + + Returns: + str: string circuit diagram. + """ + return UnicodeCircuitDiagram._build(circuit) + + @classmethod + def _vertical_delimiter(cls) -> str: + """Character that connects qubits of multi-qubit gates.""" + return "│" + + @classmethod + def _qubit_line_character(cls) -> str: + """Character used for the qubit line.""" + return "─" + + @classmethod + def _box_pad(cls) -> int: + """number of blank space characters around the gate name.""" + return 4 + + @classmethod + def _qubit_line_spacing_above(cls) -> int: + """number of empty lines above the qubit line.""" + return 1 + + @classmethod + def _qubit_line_spacing_below(cls) -> int: + """number of empty lines below the qubit line.""" + return 1 + + @classmethod + def _duplicate_time_at_bottom(cls, lines: list) -> None: + # Do not add a line after the circuit + # It is safe to do because the last line is empty: _qubit_line_spacing["after"] = 1 + lines[-1] = lines[0] + + @classmethod + def _create_diagram_column( + cls, + circuit_qubits: QubitSet, + items: list[Instruction | ResultType], + global_phase: float | None = None, + ) -> str: + """Return a column in the string diagram of the circuit for a given list of items. + + Args: + circuit_qubits (QubitSet): qubits in circuit + items (list[Instruction | ResultType]): list of instructions or result types + global_phase (float | None): the integrated global phase up to this column + + Returns: + str: a string diagram for the specified moment in time for a column. + """ + symbols = {qubit: cls._qubit_line_character() for qubit in circuit_qubits} + connections = {qubit: "none" for qubit in circuit_qubits} + + for item in items: + ( + target_qubits, + control_qubits, + qubits, + connections, + ascii_symbols, + map_control_qubit_states, + ) = cls._build_parameters(circuit_qubits, item, connections) + + for qubit in qubits: + # Determine if the qubit is part of the item or in the middle of a + # multi qubit item. + if qubit in target_qubits: + item_qubit_index = [ + index for index, q in enumerate(target_qubits) if q == qubit + ][0] + power_string = ( + f"^{power}" + if ( + (power := getattr(item, "power", 1)) != 1 + # this has the limitation of not printing the power + # when a user has a gate genuinely named C, but + # is necessary to enable proper printing of custom + # gates with built-in control qubits + and ascii_symbols[item_qubit_index] != "C" + ) + else "" + ) + symbols[qubit] = ( + f"{ascii_symbols[item_qubit_index]}{power_string}" + if power_string + else ascii_symbols[item_qubit_index] + ) + + elif qubit in control_qubits: + symbols[qubit] = "C" if map_control_qubit_states[qubit] else "N" + else: + symbols[qubit] = "┼" + + output = cls._create_output(symbols, connections, circuit_qubits, global_phase) + return output + + @classmethod + def _build_parameters( + cls, circuit_qubits: QubitSet, item: ResultType | Instruction, connections: dict[Qubit, str] + ) -> tuple: + map_control_qubit_states = {} + + if (isinstance(item, ResultType) and not item.target) or ( + isinstance(item, Instruction) and isinstance(item.operator, CompilerDirective) + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + qubits = circuit_qubits + ascii_symbols = [item.ascii_symbols[0]] * len(qubits) + cls._update_connections(qubits, connections) + elif ( + isinstance(item, Instruction) + and isinstance(item.operator, Gate) + and item.operator.name == "GPhase" + ): + target_qubits = circuit_qubits + control_qubits = QubitSet() + qubits = circuit_qubits + ascii_symbols = cls._qubit_line_character() * len(circuit_qubits) + else: + if isinstance(item.target, list): + target_qubits = reduce(QubitSet.union, map(QubitSet, item.target), QubitSet()) + else: + target_qubits = item.target + control_qubits = getattr(item, "control", QubitSet()) + control_state = getattr(item, "control_state", "1" * len(control_qubits)) + map_control_qubit_states = { + qubit: state for qubit, state in zip(control_qubits, control_state) + } + + target_and_control = target_qubits.union(control_qubits) + qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) + ascii_symbols = item.ascii_symbols + cls._update_connections(qubits, connections) + + return ( + target_qubits, + control_qubits, + qubits, + connections, + ascii_symbols, + map_control_qubit_states, + ) + + @staticmethod + def _update_connections(qubits: QubitSet, connections: dict[Qubit, str]) -> None: + if len(qubits) > 1: + connections |= {qubit: "both" for qubit in qubits[1:-1]} + connections[qubits[-1]] = "above" + connections[qubits[0]] = "below" + + # Ignore flake8 issue caused by Literal["above", "below", "both", "none"] + # flake8: noqa: BCS005 + @classmethod + def _draw_symbol( + cls, + symbol: str, + symbols_width: int, + connection: Literal["above", "below", "both", "none"], + ) -> str: + """Create a string representing the symbol inside a box. + + Args: + symbol (str): the gate name + symbols_width (int): size of the expected output. The ouput will be filled with + cls._qubit_line_character() if needed. + connection (Literal["above", "below", "both", "none"]): specifies if a connection + will be drawn above and/or below the box. + + Returns: + str: a string representing the symbol. + """ + top = "" + bottom = "" + if symbol in ["C", "N", "SWAP"]: + if connection in ["above", "both"]: + top = _fill_symbol(cls._vertical_delimiter(), " ") + if connection in ["below", "both"]: + bottom = _fill_symbol(cls._vertical_delimiter(), " ") + new_symbol = {"C": "●", "N": "◯", "SWAP": "x"} + # replace SWAP by x + # the size of the moment remains as if there was a box with 4 characters inside + symbol = _fill_symbol(new_symbol[symbol], cls._qubit_line_character()) + elif symbol in ["StartVerbatim", "EndVerbatim"]: + top, symbol, bottom = cls._build_verbatim_box(symbol, connection) + elif symbol == "┼": + top = bottom = _fill_symbol(cls._vertical_delimiter(), " ") + symbol = _fill_symbol(f"{symbol}", cls._qubit_line_character()) + elif symbol == cls._qubit_line_character(): + # We do not box when no gate is applied. + pass + else: + top, symbol, bottom = cls._build_box(symbol, connection) + + output = f"{_fill_symbol(top, ' ', symbols_width)} \n" + output += f"{_fill_symbol(symbol, cls._qubit_line_character(), symbols_width)}{cls._qubit_line_character()}\n" + output += f"{_fill_symbol(bottom, ' ', symbols_width)} \n" + return output + + @staticmethod + def _build_box( + symbol: str, connection: Literal["above", "below", "both", "none"] + ) -> tuple[str, str, str]: + top_edge_symbol = "┴" if connection in ["above", "both"] else "─" + top = f"┌─{_fill_symbol(top_edge_symbol, '─', len(symbol))}─┐" + + bottom_edge_symbol = "┬" if connection in ["below", "both"] else "─" + bottom = f"└─{_fill_symbol(bottom_edge_symbol, '─', len(symbol))}─┘" + + symbol = f"┤ {symbol} ├" + return top, symbol, bottom + + @classmethod + def _build_verbatim_box( + cls, + symbol: Literal["StartVerbatim", "EndVerbatim"], + connection: Literal["above", "below", "both", "none"], + ) -> str: + top = "" + bottom = "" + if connection == "below": + bottom = "║" + elif connection == "both": + top = bottom = "║" + symbol = "║" + elif connection == "above": + top = "║" + symbol = "╨" + top = _fill_symbol(top, " ") + symbol = _fill_symbol(symbol, cls._qubit_line_character()) + bottom = _fill_symbol(bottom, " ") + + return top, symbol, bottom + + +def _fill_symbol(symbol: str, filler: str, width: int | None = None) -> str: + return "{0:{fill}{align}{width}}".format( + symbol, + fill=filler, + align="^", + width=width if width is not None else len(symbol), + ) diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 916bfb050..88e32d92f 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -26,6 +26,10 @@ from braket.pulse import Frame, Port, PulseSequence +def _assert_correct_diagram(circ, expected): + assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(expected) + + def test_empty_circuit(): assert AsciiCircuitDiagram.build_diagram(Circuit()) == "" @@ -787,10 +791,6 @@ def test_pulse_gate_multi_qubit_circuit(): _assert_correct_diagram(circ, expected) -def _assert_correct_diagram(circ, expected): - assert AsciiCircuitDiagram.build_diagram(circ) == "\n".join(expected) - - def test_circuit_with_nested_target_list(): circ = ( Circuit() diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 91b19dba9..ecaad12bf 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -18,7 +18,6 @@ import braket.ir.jaqcd as jaqcd from braket.circuits import ( - AsciiCircuitDiagram, Circuit, FreeParameter, FreeParameterExpression, @@ -28,6 +27,7 @@ Observable, QubitSet, ResultType, + UnicodeCircuitDiagram, circuit, compiler_directives, gates, @@ -199,7 +199,7 @@ def test_repr_result_types(cnot_prob): def test_str(h): - expected = AsciiCircuitDiagram.build_diagram(h) + expected = UnicodeCircuitDiagram.build_diagram(h) assert str(h) == expected diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py new file mode 100644 index 000000000..96df634cb --- /dev/null +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -0,0 +1,1021 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +import numpy as np +import pytest + +from braket.circuits import ( + Circuit, + FreeParameter, + Gate, + Instruction, + Observable, + Operator, + UnicodeCircuitDiagram, +) +from braket.pulse import Frame, Port, PulseSequence + + +def _assert_correct_diagram(circ, expected): + assert UnicodeCircuitDiagram.build_diagram(circ) == "\n".join(expected) + + +def test_empty_circuit(): + assert UnicodeCircuitDiagram.build_diagram(Circuit()) == "" + + +def test_only_gphase_circuit(): + assert UnicodeCircuitDiagram.build_diagram(Circuit().gphase(0.1)) == "Global phase: 0.1" + + +def test_one_gate_one_qubit(): + circ = Circuit().h(0) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─", + " └───┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_one_qubit_rotation(): + circ = Circuit().rx(angle=3.14, target=0) + # Column formats to length of the gate plus the ascii representation for the angle. + expected = ( + "T : │ 0 │", + " ┌──────────┐ ", + "q0 : ─┤ Rx(3.14) ├─", + " └──────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_one_qubit_rotation_with_parameter(): + theta = FreeParameter("theta") + circ = Circuit().rx(angle=theta, target=0) + # Column formats to length of the gate plus the ascii representation for the angle. + expected = ( + "T : │ 0 │", + " ┌───────────┐ ", + "q0 : ─┤ Rx(theta) ├─", + " └───────────┘ ", + "T : │ 0 │", + "", + "Unassigned parameters: [theta].", + ) + _assert_correct_diagram(circ, expected) + + +@pytest.mark.parametrize("target", [0, 1]) +def test_one_gate_with_global_phase(target): + circ = Circuit().x(target=target).gphase(0.15) + expected = ( + "T : │ 0 │ 1 │", + "GP : │ 0 │0.15 │", + " ┌───┐ ", + f"q{target} : ─┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + "", + "Global phase: 0.15", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_with_zero_global_phase(): + circ = Circuit().gphase(-0.15).x(target=0).gphase(0.15) + expected = ( + "T : │ 0 │ 1 │", + "GP : │-0.15│0.00 │", + " ┌───┐ ", + "q0 : ─┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_one_qubit_rotation_with_unicode(): + theta = FreeParameter("\u03B8") + circ = Circuit().rx(angle=theta, target=0) + # Column formats to length of the gate plus the ascii representation for the angle. + expected = ( + "T : │ 0 │", + " ┌───────┐ ", + "q0 : ─┤ Rx(θ) ├─", + " └───────┘ ", + "T : │ 0 │", + "", + "Unassigned parameters: [θ].", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_with_parametric_expression_global_phase_(): + theta = FreeParameter("\u03B8") + circ = Circuit().x(target=0).gphase(2 * theta).x(0).gphase(1) + expected = ( + "T : │ 0 │ 1 │ 2 │", + "GP : │ 0 │ 2*θ │2*θ + 1.0│", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ X ├─┤ X ├───────────", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │", + "", + "Global phase: 2*θ + 1.0", + "", + "Unassigned parameters: [θ].", + ) + _assert_correct_diagram(circ, expected) + + +def test_one_gate_one_qubit_rotation_with_parameter_assigned(): + theta = FreeParameter("theta") + circ = Circuit().rx(angle=theta, target=0) + new_circ = circ.make_bound_circuit({"theta": np.pi}) + # Column formats to length of the gate plus the ascii representation for the angle. + expected = ( + "T : │ 0 │", + " ┌──────────┐ ", + "q0 : ─┤ Rx(3.14) ├─", + " └──────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(new_circ, expected) + + +def test_qubit_width(): + circ = Circuit().h(0).h(100) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─", + " └───┘ ", + " ┌───┐ ", + "q100 : ─┤ H ├─", + " └───┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_different_size_boxes(): + circ = Circuit().cnot(0, 1).rx(2, 0.3) + expected = ( + "T : │ 0 │", + " ", + "q0 : ──────●───────", + " │ ", + " ┌─┴─┐ ", + "q1 : ────┤ X ├─────", + " └───┘ ", + " ┌──────────┐ ", + "q2 : ─┤ Rx(0.30) ├─", + " └──────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_swap(): + circ = Circuit().swap(0, 2).x(1) + expected = ( + "T : │ 0 │", + " ", + "q0 : ────x───────────", + " │ ", + " │ ┌───┐ ", + "q1 : ────┼─────┤ X ├─", + " │ └───┘ ", + " │ ", + "q2 : ────x───────────", + " ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_gate_width(): + class Foo(Gate): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["FOO"]) + + def to_ir(self, target): + return "foo" + + circ = Circuit().h(0).h(1).add_instruction(Instruction(Foo(), 0)) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ┌─────┐ ", + "q0 : ─┤ H ├─┤ FOO ├─", + " └───┘ └─────┘ ", + " ┌───┐ ", + "q1 : ─┤ H ├─────────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_time_width(): + circ = Circuit() + num_qubits = 8 + for qubit in range(num_qubits): + if qubit == num_qubits - 1: + break + circ.cnot(qubit, qubit + 1) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + " ", + "q0 : ───●───────────────────────────────────────", + " │ ", + " ┌─┴─┐ ", + "q1 : ─┤ X ├───●─────────────────────────────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q2 : ───────┤ X ├───●───────────────────────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q3 : ─────────────┤ X ├───●─────────────────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q4 : ───────────────────┤ X ├───●───────────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q5 : ─────────────────────────┤ X ├───●─────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q6 : ───────────────────────────────┤ X ├───●───", + " └───┘ │ ", + " ┌─┴─┐ ", + "q7 : ─────────────────────────────────────┤ X ├─", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_connector_across_two_qubits(): + circ = Circuit().cnot(4, 3).h(range(2, 6)) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q2 : ─┤ H ├───────", + " └───┘ ", + " ┌───┐ ┌───┐ ", + "q3 : ─┤ X ├─┤ H ├─", + " └─┬─┘ └───┘ ", + " │ ┌───┐ ", + "q4 : ───●───┤ H ├─", + " └───┘ ", + " ┌───┐ ", + "q5 : ─┤ H ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_neg_control_qubits(): + circ = Circuit().x(1, control=[0, 2], control_state=[0, 1]) + expected = ( + "T : │ 0 │", + " ", + "q0 : ───◯───", + " │ ", + " ┌─┴─┐ ", + "q1 : ─┤ X ├─", + " └─┬─┘ ", + " │ ", + "q2 : ───●───", + " ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_only_neg_control_qubits(): + circ = Circuit().x(2, control=[0, 1], control_state=0) + expected = ( + "T : │ 0 │", + " ", + "q0 : ───◯───", + " │ ", + " │ ", + "q1 : ───◯───", + " │ ", + " ┌─┴─┐ ", + "q2 : ─┤ X ├─", + " └───┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_connector_across_three_qubits(): + circ = Circuit().x(control=(3, 4), target=5).h(range(2, 6)) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q2 : ─┤ H ├───────", + " └───┘ ", + " ┌───┐ ", + "q3 : ───●───┤ H ├─", + " │ └───┘ ", + " │ ┌───┐ ", + "q4 : ───●───┤ H ├─", + " │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q5 : ─┤ X ├─┤ H ├─", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_overlapping_qubits(): + circ = Circuit().cnot(0, 2).x(control=1, target=3).h(0) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q0 : ───●─────────┤ H ├─", + " │ └───┘ ", + " │ ", + "q1 : ───┼─────●─────────", + " │ │ ", + " ┌─┴─┐ │ ", + "q2 : ─┤ X ├───┼─────────", + " └───┘ │ ", + " ┌─┴─┐ ", + "q3 : ───────┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_overlapping_qubits_angled_gates(): + circ = Circuit().zz(0, 2, 0.15).x(control=1, target=3).h(0) + expected = ( + "T : │ 0 │ 1 │", + " ┌──────────┐ ┌───┐ ", + "q0 : ─┤ ZZ(0.15) ├───────┤ H ├─", + " └────┬─────┘ └───┘ ", + " │ ", + "q1 : ──────┼─────────●─────────", + " │ │ ", + " ┌────┴─────┐ │ ", + "q2 : ─┤ ZZ(0.15) ├───┼─────────", + " └──────────┘ │ ", + " ┌─┴─┐ ", + "q3 : ──────────────┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_connector_across_gt_two_qubits(): + circ = Circuit().h(4).x(control=3, target=5).h(4).h(2) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q2 : ─┤ H ├─────────────", + " └───┘ ", + " ", + "q3 : ─────────●─────────", + " │ ", + " ┌───┐ │ ┌───┐ ", + "q4 : ─┤ H ├───┼───┤ H ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ", + "q5 : ───────┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_connector_across_non_used_qubits(): + circ = Circuit().h(4).cnot(3, 100).h(4).h(101) + expected = ( + "T : │ 0 │ 1 │", + " ", + "q3 : ─────────●─────────", + " │ ", + " ┌───┐ │ ┌───┐ ", + "q4 : ─┤ H ├───┼───┤ H ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ", + "q100 : ───────┤ X ├───────", + " └───┘ ", + " ┌───┐ ", + "q101 : ─┤ H ├─────────────", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_1q_no_preceding(): + circ = Circuit().add_verbatim_box(Circuit().h(0)) + expected = ( + "T : │ 0 │ 1 │ 2 │", + " ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───EndVerbatim───", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_1q_preceding(): + circ = Circuit().h(0).add_verbatim_box(Circuit().h(0)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───StartVerbatim───┤ H ├───EndVerbatim───", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_1q_following(): + circ = Circuit().add_verbatim_box(Circuit().h(0)).h(0) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │", + " ┌───┐ ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───EndVerbatim───┤ H ├─", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_2q_no_preceding(): + circ = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │", + " ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───●─────EndVerbatim───", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ─────────╨───────────────┤ X ├────────╨────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_2q_preceding(): + circ = Circuit().h(0).add_verbatim_box(Circuit().h(0).cnot(0, 1)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───StartVerbatim───┤ H ├───●─────EndVerbatim───", + " └───┘ ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ───────────────╨───────────────┤ X ├────────╨────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_2q_following(): + circ = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1)).h(0) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───●─────EndVerbatim───┤ H ├─", + " ║ └───┘ │ ║ └───┘ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ─────────╨───────────────┤ X ├────────╨──────────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_3q_no_preceding(): + circ = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1).cnot(1, 2)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───●───────────EndVerbatim───", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ─────────║───────────────┤ X ├───●──────────║────────", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q2 : ─────────╨─────────────────────┤ X ├────────╨────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_3q_preceding(): + circ = Circuit().h(0).add_verbatim_box(Circuit().h(0).cnot(0, 1).cnot(1, 2)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───StartVerbatim───┤ H ├───●───────────EndVerbatim───", + " └───┘ ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ───────────────║───────────────┤ X ├───●──────────║────────", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q2 : ───────────────╨─────────────────────┤ X ├────────╨────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_3q_following(): + circ = Circuit().add_verbatim_box(Circuit().h(0).cnot(0, 1).cnot(1, 2)).h(0) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │", + " ┌───┐ ┌───┐ ", + "q0 : ───StartVerbatim───┤ H ├───●───────────EndVerbatim───┤ H ├─", + " ║ └───┘ │ ║ └───┘ ", + " ║ ┌─┴─┐ ║ ", + "q1 : ─────────║───────────────┤ X ├───●──────────║──────────────", + " ║ └───┘ │ ║ ", + " ║ ┌─┴─┐ ║ ", + "q2 : ─────────╨─────────────────────┤ X ├────────╨──────────────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_different_qubits(): + circ = Circuit().h(1).add_verbatim_box(Circuit().h(0)).cnot(3, 4) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ", + "q0 : ─────────StartVerbatim───┤ H ├───EndVerbatim─────────", + " ║ └───┘ ║ ", + " ┌───┐ ║ ║ ", + "q1 : ─┤ H ├─────────║──────────────────────║──────────────", + " └───┘ ║ ║ ", + " ║ ║ ", + "q3 : ───────────────║──────────────────────║──────────●───", + " ║ ║ │ ", + " ║ ║ ┌─┴─┐ ", + "q4 : ───────────────╨──────────────────────╨────────┤ X ├─", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_verbatim_qubset_qubits(): + circ = Circuit().h(1).cnot(0, 1).cnot(1, 2).add_verbatim_box(Circuit().h(1)).cnot(2, 3) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + " ", + "q0 : ─────────●───────────StartVerbatim───────────EndVerbatim─────────", + " │ ║ ║ ", + " ┌───┐ ┌─┴─┐ ║ ┌───┐ ║ ", + "q1 : ─┤ H ├─┤ X ├───●───────────║─────────┤ H ├────────║──────────────", + " └───┘ └───┘ │ ║ └───┘ ║ ", + " ┌─┴─┐ ║ ║ ", + "q2 : ─────────────┤ X ├─────────║──────────────────────║──────────●───", + " └───┘ ║ ║ │ ", + " ║ ║ ┌─┴─┐ ", + "q3 : ───────────────────────────╨──────────────────────╨────────┤ X ├─", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_ignore_non_gates(): + class Foo(Operator): + @property + def name(self) -> str: + return "foo" + + def to_ir(self, target): + return "foo" + + circ = Circuit().h(0).h(1).cnot(1, 2).add_instruction(Instruction(Foo(), 0)) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ", + "q0 : ─┤ H ├───────", + " └───┘ ", + " ┌───┐ ", + "q1 : ─┤ H ├───●───", + " └───┘ │ ", + " ┌─┴─┐ ", + "q2 : ───────┤ X ├─", + " └───┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_single_qubit_result_types_target_none(): + circ = Circuit().h(0).probability() + expected = ( + "T : │ 0 │ Result Types │", + " ┌───┐ ┌─────────────┐ ", + "q0 : ─┤ H ├─┤ Probability ├─", + " └───┘ └─────────────┘ ", + "T : │ 0 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_result_types_target_none(): + circ = Circuit().h(0).h(100).probability() + expected = ( + "T : │ 0 │ Result Types │", + " ┌───┐ ┌─────────────┐ ", + "q0 : ─┤ H ├─┤ Probability ├─", + " └───┘ └──────┬──────┘ ", + " ┌───┐ ┌──────┴──────┐ ", + "q100 : ─┤ H ├─┤ Probability ├─", + " └───┘ └─────────────┘ ", + "T : │ 0 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_result_types_target_some(): + circ = ( + Circuit() + .h(0) + .h(1) + .h(100) + .expectation(observable=Observable.Y() @ Observable.Z(), target=[0, 100]) + ) + expected = ( + "T : │ 0 │ Result Types │", + " ┌───┐ ┌──────────────────┐ ", + "q0 : ─┤ H ├─┤ Expectation(Y@Z) ├─", + " └───┘ └────────┬─────────┘ ", + " ┌───┐ │ ", + "q1 : ─┤ H ├──────────┼───────────", + " └───┘ │ ", + " ┌───┐ ┌────────┴─────────┐ ", + "q100 : ─┤ H ├─┤ Expectation(Y@Z) ├─", + " └───┘ └──────────────────┘ ", + "T : │ 0 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_additional_result_types(): + circ = Circuit().h(0).h(1).h(100).state_vector().amplitude(["110", "001"]) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─", + " └───┘ ", + " ┌───┐ ", + "q1 : ─┤ H ├─", + " └───┘ ", + " ┌───┐ ", + "q100 : ─┤ H ├─", + " └───┘ ", + "T : │ 0 │", + "", + "Additional result types: StateVector, Amplitude(110,001)", + ) + _assert_correct_diagram(circ, expected) + + +def test_multiple_result_types(): + circ = ( + Circuit() + .cnot(0, 2) + .cnot(1, 3) + .h(0) + .variance(observable=Observable.Y(), target=0) + .expectation(observable=Observable.Y(), target=2) + .sample(observable=Observable.Y()) + ) + expected = ( + "T : │ 0 │ 1 │ Result Types │", + " ┌───┐ ┌─────────────┐ ┌───────────┐ ", + "q0 : ───●─────────┤ H ├──┤ Variance(Y) ├───┤ Sample(Y) ├─", + " │ └───┘ └─────────────┘ └─────┬─────┘ ", + " │ ┌─────┴─────┐ ", + "q1 : ───┼─────●────────────────────────────┤ Sample(Y) ├─", + " │ │ └─────┬─────┘ ", + " ┌─┴─┐ │ ┌────────────────┐ ┌─────┴─────┐ ", + "q2 : ─┤ X ├───┼─────────┤ Expectation(Y) ├─┤ Sample(Y) ├─", + " └───┘ │ └────────────────┘ └─────┬─────┘ ", + " ┌─┴─┐ ┌─────┴─────┐ ", + "q3 : ───────┤ X ├──────────────────────────┤ Sample(Y) ├─", + " └───┘ └───────────┘ ", + "T : │ 0 │ 1 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_multiple_result_types_with_state_vector_amplitude(): + circ = ( + Circuit() + .cnot(0, 2) + .cnot(1, 3) + .h(0) + .variance(observable=Observable.Y(), target=0) + .expectation(observable=Observable.Y(), target=3) + .expectation(observable=Observable.Hermitian(np.array([[1.0, 0.0], [0.0, 1.0]])), target=1) + .amplitude(["0001"]) + .state_vector() + ) + expected = ( + "T : │ 0 │ 1 │ Result Types │", + " ┌───┐ ┌─────────────┐ ", + "q0 : ───●─────────┤ H ├──────┤ Variance(Y) ├───────", + " │ └───┘ └─────────────┘ ", + " │ ┌────────────────────────┐ ", + "q1 : ───┼─────●─────────┤ Expectation(Hermitian) ├─", + " │ │ └────────────────────────┘ ", + " ┌─┴─┐ │ ", + "q2 : ─┤ X ├───┼────────────────────────────────────", + " └───┘ │ ", + " ┌─┴─┐ ┌────────────────┐ ", + "q3 : ───────┤ X ├───────────┤ Expectation(Y) ├─────", + " └───┘ └────────────────┘ ", + "T : │ 0 │ 1 │ Result Types │", + "", + "Additional result types: Amplitude(0001), StateVector", + ) + _assert_correct_diagram(circ, expected) + + +def test_multiple_result_types_with_custom_hermitian_ascii_symbol(): + herm_matrix = (Observable.Y() @ Observable.Z()).to_matrix() + circ = ( + Circuit() + .cnot(0, 2) + .cnot(1, 3) + .h(0) + .variance(observable=Observable.Y(), target=0) + .expectation(observable=Observable.Y(), target=3) + .expectation( + observable=Observable.Hermitian( + matrix=herm_matrix, + display_name="MyHerm", + ), + target=[1, 2], + ) + ) + expected = ( + "T : │ 0 │ 1 │ Result Types │", + " ┌───┐ ┌─────────────┐ ", + "q0 : ───●─────────┤ H ├─────┤ Variance(Y) ├─────", + " │ └───┘ └─────────────┘ ", + " │ ┌─────────────────────┐ ", + "q1 : ───┼─────●─────────┤ Expectation(MyHerm) ├─", + " │ │ └──────────┬──────────┘ ", + " ┌─┴─┐ │ ┌──────────┴──────────┐ ", + "q2 : ─┤ X ├───┼─────────┤ Expectation(MyHerm) ├─", + " └───┘ │ └─────────────────────┘ ", + " ┌─┴─┐ ┌────────────────┐ ", + "q3 : ───────┤ X ├─────────┤ Expectation(Y) ├────", + " └───┘ └────────────────┘ ", + "T : │ 0 │ 1 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_noise_1qubit(): + circ = Circuit().h(0).x(1).bit_flip(1, 0.1) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─────────────", + " └───┘ ", + " ┌───┐ ┌─────────┐ ", + "q1 : ─┤ X ├─┤ BF(0.1) ├─", + " └───┘ └─────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_noise_2qubit(): + circ = Circuit().h(1).kraus((0, 2), [np.eye(4)]) + expected = ( + "T : │ 0 │", + " ┌────┐ ", + "q0 : ───────┤ KR ├─", + " └─┬──┘ ", + " ┌───┐ │ ", + "q1 : ─┤ H ├───┼────", + " └───┘ │ ", + " ┌─┴──┐ ", + "q2 : ───────┤ KR ├─", + " └────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_noise_multi_probabilities(): + circ = Circuit().h(0).x(1).pauli_channel(1, 0.1, 0.2, 0.3) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├─────────────────────", + " └───┘ ", + " ┌───┐ ┌─────────────────┐ ", + "q1 : ─┤ X ├─┤ PC(0.1,0.2,0.3) ├─", + " └───┘ └─────────────────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_noise_multi_probabilities_with_parameter(): + a = FreeParameter("a") + b = FreeParameter("b") + c = FreeParameter("c") + circ = Circuit().h(0).x(1).pauli_channel(1, a, b, c) + expected = ( + "T : │ 0 │", + " ┌───┐ ", + "q0 : ─┤ H ├───────────────", + " └───┘ ", + " ┌───┐ ┌───────────┐ ", + "q1 : ─┤ X ├─┤ PC(a,b,c) ├─", + " └───┘ └───────────┘ ", + "T : │ 0 │", + "", + "Unassigned parameters: [a, b, c].", + ) + _assert_correct_diagram(circ, expected) + + +def test_pulse_gate_1_qubit_circuit(): + circ = ( + Circuit() + .h(0) + .pulse_gate(0, PulseSequence().set_phase(Frame("x", Port("px", 1e-9), 1e9, 0), 0)) + ) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ┌────┐ ", + "q0 : ─┤ H ├─┤ PG ├─", + " └───┘ └────┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_pulse_gate_multi_qubit_circuit(): + circ = ( + Circuit() + .h(0) + .pulse_gate([0, 1], PulseSequence().set_phase(Frame("x", Port("px", 1e-9), 1e9, 0), 0)) + ) + expected = ( + "T : │ 0 │ 1 │", + " ┌───┐ ┌────┐ ", + "q0 : ─┤ H ├─┤ PG ├─", + " └───┘ └─┬──┘ ", + " ┌─┴──┐ ", + "q1 : ───────┤ PG ├─", + " └────┘ ", + "T : │ 0 │ 1 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_circuit_with_nested_target_list(): + circ = ( + Circuit() + .h(0) + .h(1) + .expectation( + observable=(2 * Observable.Y()) @ (-3 * Observable.I()) + - 0.75 * Observable.Y() @ Observable.Z(), + target=[[0, 1], [0, 1]], + ) + ) + + expected = ( + "T : │ 0 │ Result Types │", + " ┌───┐ ┌──────────────────────────┐ ", + "q0 : ─┤ H ├─┤ Expectation(Hamiltonian) ├─", + " └───┘ └────────────┬─────────────┘ ", + " ┌───┐ ┌────────────┴─────────────┐ ", + "q1 : ─┤ H ├─┤ Expectation(Hamiltonian) ├─", + " └───┘ └──────────────────────────┘ ", + "T : │ 0 │ Result Types │", + ) + _assert_correct_diagram(circ, expected) + + +def test_hamiltonian(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .rx(0, FreeParameter("theta")) + .adjoint_gradient( + 4 * (2e-5 * Observable.Z() + 2 * (3 * Observable.X() @ (2 * Observable.Y()))), + [[0], [1, 2]], + ) + ) + expected = ( + "T : │ 0 │ 1 │ 2 │ Result Types │", + " ┌───┐ ┌───────────┐ ┌──────────────────────────────┐ ", + "q0 : ─┤ H ├───●───┤ Rx(theta) ├─┤ AdjointGradient(Hamiltonian) ├─", + " └───┘ │ └───────────┘ └──────────────┬───────────────┘ ", + " ┌─┴─┐ ┌──────────────┴───────────────┐ ", + "q1 : ───────┤ X ├───────────────┤ AdjointGradient(Hamiltonian) ├─", + " └───┘ └──────────────┬───────────────┘ ", + " ┌──────────────┴───────────────┐ ", + "q2 : ───────────────────────────┤ AdjointGradient(Hamiltonian) ├─", + " └──────────────────────────────┘ ", + "T : │ 0 │ 1 │ 2 │ Result Types │", + "", + "Unassigned parameters: [theta].", + ) + _assert_correct_diagram(circ, expected) + + +def test_power(): + class Foo(Gate): + def __init__(self): + super().__init__(qubit_count=1, ascii_symbols=["FOO"]) + + class CFoo(Gate): + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["C", "FOO"]) + + class FooFoo(Gate): + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["FOO", "FOO"]) + + circ = Circuit().h(0, power=1).h(1, power=0).h(2, power=-3.14) + circ.add_instruction(Instruction(Foo(), 0, power=-1)) + circ.add_instruction(Instruction(CFoo(), (0, 1), power=2)) + circ.add_instruction(Instruction(CFoo(), (1, 2), control=0, power=3)) + circ.add_instruction(Instruction(FooFoo(), (1, 3), control=[0, 2], power=4)) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌────────┐ ", + "q0 : ────┤ H ├────┤ FOO^-1 ├─────●─────────●─────────●─────", + " └───┘ └────────┘ │ │ │ ", + " ┌─────┐ ┌───┴───┐ │ ┌───┴───┐ ", + "q1 : ───┤ H^0 ├──────────────┤ FOO^2 ├─────●─────┤ FOO^4 ├─", + " └─────┘ └───────┘ │ └───┬───┘ ", + " ┌─────────┐ ┌───┴───┐ │ ", + "q2 : ─┤ H^-3.14 ├──────────────────────┤ FOO^3 ├─────●─────", + " └─────────┘ └───────┘ │ ", + " ┌───┴───┐ ", + "q3 : ────────────────────────────────────────────┤ FOO^4 ├─", + " └───────┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_unbalanced_ascii_symbols(): + class FooFoo(Gate): + def __init__(self): + super().__init__(qubit_count=2, ascii_symbols=["FOOO", "FOO"]) + + circ = Circuit().add_instruction(Instruction(FooFoo(), (1, 3), control=[0, 2], power=4)) + expected = ( + "T : │ 0 │", + " ", + "q0 : ─────●──────", + " │ ", + " ┌───┴────┐ ", + "q1 : ─┤ FOOO^4 ├─", + " └───┬────┘ ", + " │ ", + "q2 : ─────●──────", + " │ ", + " ┌───┴───┐ ", + "q3 : ─┤ FOO^4 ├──", + " └───────┘ ", + "T : │ 0 │", + ) + _assert_correct_diagram(circ, expected) From da081b754bec8e27e9eb54b263f689d65a66bc05 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 7 Mar 2024 16:13:47 +0000 Subject: [PATCH 19/98] prepare release v1.73.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62d625ca2..150c88ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.73.0 (2024-03-07) + +### Features + + * update circuit drawing + ## v1.72.2 (2024-03-04) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index b77e9d94c..68f5f1a4c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.72.3.dev0" +__version__ = "1.73.0" From b4955aa4ac57d07c89f509aa14fad6aa65bc718e Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 7 Mar 2024 16:13:47 +0000 Subject: [PATCH 20/98] update development version to v1.73.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 68f5f1a4c..52d1e573d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.0" +__version__ = "1.73.1.dev0" From 5deeac3e8506e1d5eccc90ccbf53c7071415c975 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 7 Mar 2024 10:53:42 -0800 Subject: [PATCH 21/98] fix: allow for braket endpoint to be set within the jobs (#902) * fix: allow for braket endpoint to be set within the jobs * Update test/integ_tests/conftest.py Co-authored-by: Cody Wang --------- Co-authored-by: Coull Co-authored-by: Cody Wang --- test/integ_tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/integ_tests/conftest.py b/test/integ_tests/conftest.py index 2a56d8074..b8d0d43df 100644 --- a/test/integ_tests/conftest.py +++ b/test/integ_tests/conftest.py @@ -36,6 +36,8 @@ def pytest_configure_node(node): """xdist hook""" node.workerinput["JOB_COMPLETED_NAME"] = job_complete_name node.workerinput["JOB_FAILED_NAME"] = job_fail_name + if endpoint := os.getenv("BRAKET_ENDPOINT"): + node.workerinput["BRAKET_ENDPOINT"] = endpoint def pytest_xdist_node_collection_finished(ids): From 1057756824a9e914f9cf65ddacf52ea11475b994 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 11 Mar 2024 16:17:24 +0000 Subject: [PATCH 22/98] prepare release v1.73.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 150c88ddd..1040afb39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.73.1 (2024-03-11) + +### Bug Fixes and Other Changes + + * allow for braket endpoint to be set within the jobs + ## v1.73.0 (2024-03-07) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 52d1e573d..d43c3860c 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.1.dev0" +__version__ = "1.73.1" From 1106c6cbd13e66eaf2e299e9f4c413772c6276f2 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 11 Mar 2024 16:17:24 +0000 Subject: [PATCH 23/98] update development version to v1.73.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d43c3860c..ba58da846 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.1" +__version__ = "1.73.2.dev0" From 9811b9b17a3b550989ddc73fb51c779a2f304e00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:32:33 -0700 Subject: [PATCH 24/98] infra: bump codecov/codecov-action from 4.0.1 to 4.1.0 (#906) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4.0.1 to 4.1.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/e0b68c6749509c5f83f984dd99a76a1c1a231044...54bcd8715eee62d40e33596ef5e8f0f48dbbccab) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 516f93c70..f4ac4e916 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -36,7 +36,7 @@ jobs: run: | tox -e unit-tests - name: Upload coverage report to Codecov - uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1 + uses: codecov/codecov-action@54bcd8715eee62d40e33596ef5e8f0f48dbbccab # v4.1.0 with: token: ${{ secrets.CODECOV_TOKEN }} if: ${{ strategy.job-index }} == 0 From 40219cd359716a106a3e67351e8660b652682a3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:39:42 -0700 Subject: [PATCH 25/98] infra: bump pypa/gh-action-pypi-publish from 1.8.12 to 1.8.14 (#907) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.12 to 1.8.14. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/e53eb8b103ffcb59469888563dc324e3c8ba6f06...81e9d935c883d0b210363ab89cf05f3894778450) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/publish-to-pypi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 0ec7ff9b2..a6a359e93 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -26,6 +26,6 @@ jobs: - name: Build a binary wheel and a source tarball run: python setup.py sdist bdist_wheel - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@e53eb8b103ffcb59469888563dc324e3c8ba6f06 # release/v1 + uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # release/v1 with: password: ${{ secrets.pypi_token }} From 628781d6cdcea687cf41392dbc6cf920f42500b6 Mon Sep 17 00:00:00 2001 From: Milan <30416311+krneta@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:52:40 -0700 Subject: [PATCH 26/98] fix: increase tol value for our integ tests (#912) --- test/integ_tests/gate_model_device_testing_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 191084145..41f20da8e 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -27,7 +27,7 @@ def get_tol(shots: int) -> Dict[str, float]: - return {"atol": 0.1, "rtol": 0.15} if shots else {"atol": 0.01, "rtol": 0} + return {"atol": 0.2, "rtol": 0.3} if shots else {"atol": 0.01, "rtol": 0} def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): From 9e3ccda6b77c67c0246448812c54d7df4dfe0cf8 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 13 Mar 2024 21:06:57 +0000 Subject: [PATCH 27/98] prepare release v1.73.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1040afb39..a26d15ed5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.73.2 (2024-03-13) + +### Bug Fixes and Other Changes + + * increase tol value for our integ tests + ## v1.73.1 (2024-03-11) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index ba58da846..3aede694e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.2.dev0" +__version__ = "1.73.2" From 22955b1d6567513e5e30709fb010352e2a83ea12 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 13 Mar 2024 21:06:57 +0000 Subject: [PATCH 28/98] update development version to v1.73.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 3aede694e..d94f8a3e0 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.2" +__version__ = "1.73.3.dev0" From 5e5b2f348ae17306f1e3b5f26f21fa3548c95526 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:37:59 -0700 Subject: [PATCH 29/98] test: add in the protocol for the ssl context (#911) --- test/unit_tests/braket/jobs/test_hybrid_job.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index e757c6a69..592af6840 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -5,7 +5,7 @@ import tempfile from logging import getLogger from pathlib import Path -from ssl import SSLContext +from ssl import PROTOCOL_TLS_CLIENT, SSLContext from unittest.mock import MagicMock, patch import job_module @@ -488,7 +488,7 @@ def my_entry(*args): def test_serialization_error(aws_session): - ssl_context = SSLContext() + ssl_context = SSLContext(protocol=PROTOCOL_TLS_CLIENT) @hybrid_job(device=None, aws_session=aws_session) def fails_serialization(): From 6029c560ea2c287c9a381bf6926ad4beb8426181 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 14 Mar 2024 12:06:50 -0700 Subject: [PATCH 30/98] fix: store account id if already accessed (#908) --- src/braket/aws/aws_session.py | 11 +++++++++-- test/unit_tests/braket/aws/test_aws_session.py | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index a1ae0c7bb..d2f2099f4 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -85,7 +85,6 @@ def __init__( self.braket_client = self.boto_session.client( "braket", config=self._config, endpoint_url=os.environ.get("BRAKET_ENDPOINT") ) - self._update_user_agent() self._custom_default_bucket = bool(default_bucket) self._default_bucket = default_bucket or os.environ.get("AMZN_BRAKET_OUT_S3_BUCKET") @@ -101,6 +100,7 @@ def __init__( self._sts = None self._logs = None self._ecr = None + self._account_id = None @property def region(self) -> str: @@ -108,7 +108,14 @@ def region(self) -> str: @property def account_id(self) -> str: - return self.sts_client.get_caller_identity()["Account"] + """Gets the caller's account number. + + Returns: + str: The account number of the caller. + """ + if not self._account_id: + self._account_id = self.sts_client.get_caller_identity()["Account"] + return self._account_id @property def iam_client(self) -> client: diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index c61d22606..56d23b2e9 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -58,7 +58,6 @@ def aws_session(boto_session, braket_client, account_id): _aws_session._sts.get_caller_identity.return_value = { "Account": account_id, } - _aws_session._s3 = Mock() return _aws_session @@ -998,6 +997,12 @@ def test_upload_to_s3(aws_session): aws_session._s3.upload_file.assert_called_with(filename, bucket, key) +def test_account_id_idempotency(aws_session, account_id): + acc_id = aws_session.account_id + assert acc_id == aws_session.account_id + assert acc_id == account_id + + def test_upload_local_data(aws_session): with tempfile.TemporaryDirectory() as temp_dir: os.chdir(temp_dir) From 5ab4460b3e786d4eb992018d85c78e69de2157f1 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Mar 2024 16:13:23 +0000 Subject: [PATCH 31/98] prepare release v1.73.3 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a26d15ed5..43cb66841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.73.3 (2024-03-18) + +### Bug Fixes and Other Changes + + * store account id if already accessed + ## v1.73.2 (2024-03-13) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d94f8a3e0..f9e668a4e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.3.dev0" +__version__ = "1.73.3" From fc624bb0c07229c3f5b4de3726e13eb04f5b9beb Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 18 Mar 2024 16:13:23 +0000 Subject: [PATCH 32/98] update development version to v1.73.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f9e668a4e..55053a2de 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.3" +__version__ = "1.73.4.dev0" From 4e9ae7552a431ca5e2209be9eb261556113ecda0 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Jaskula <99367153+jcjaskula-aws@users.noreply.github.com> Date: Mon, 18 Mar 2024 17:05:26 -0400 Subject: [PATCH 33/98] feat: Allow sets of calibrations in batches (#859) * add duration type to FreeParameterExpression * consider FreeParameter as float * move to_ast to FreeParameterExprsesion * change back FPEIdentifier's parent to Identifier * clean up syntax * add precision about the expression type * add __repr__ to waveforms * do not simplify constants with defcals * add type validation * update oqpy to 0.3.2 * fix linters * increase test coverage * update to oqpy 0.3.3 * fix last merge commit * fix type hints * update to oqpy 0.3.4 * fix oqpy to 0.3.3 * remove FreeParameterExpressionIdentitifer * declare input parameters with pulse sequences * use machine-size types * create _InputVarSplitter * remove never visited branch * fix partial coverage * hacking test because the set order changes with python version * pass inputs with PulseSequence * pass empty dict to OpenQasmProgram * force FloatVar locally * add FreeDurationParameterExpression * simplify operation methods * use TYPE_CHECKING * move FreeDurationParameterExpression to pulse folder * remove TYPE_CHECKING * remove algebra rule for FreeDurationParameterExpression * accept integers as binding values * convert FreeParameter to OQpyExpression * fix coverage * clean tests * register freeparameters with delay * trigger GitHub actions * modify and rename _format_parameter_ast * update to oqpy 0.3.5 OQPy 0.3.5 converts now correctly ExpressionConvertible to duration * trigger GitHub actions * clean code * clean import * pass empty dict early * pass gate_definitions sets to batches * clean code * changes according to feedback * fix mock tests * avoid infinite loop * scale code * rename vars * fix merge * improve linters * Merge branch 'main' into jcjaskula-aws/gate_definitions_batch * fix merge * rename var * fix docstring * fix docstring --------- Co-authored-by: Cody Wang Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> Co-authored-by: Kshitij Chhabra Co-authored-by: Aaron Berdy --- src/braket/aws/aws_quantum_task.py | 22 ++--- src/braket/aws/aws_quantum_task_batch.py | 96 +++++++++++++------ src/braket/circuits/circuit.py | 71 +++++++------- test/unit_tests/braket/aws/test_aws_device.py | 13 ++- 4 files changed, 122 insertions(+), 80 deletions(-) diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index c6ad36b27..54f544159 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -105,7 +105,7 @@ def create( disable_qubit_rewiring: bool = False, tags: dict[str, str] | None = None, inputs: dict[str, float] | None = None, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] | None = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, quiet: bool = False, reservation_arn: str | None = None, *args, @@ -148,10 +148,9 @@ def create( IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. - gate_definitions (Optional[dict[tuple[Gate, QubitSet], PulseSequence]] | None): - A `Dict` for user defined gate calibration. The calibration is defined for - for a particular `Gate` on a particular `QubitSet` and is represented by - a `PulseSequence`. + gate_definitions (dict[tuple[Gate, QubitSet], PulseSequence] | None): A `dict` + of user defined gate calibrations. Each calibration is defined for a particular + `Gate` on a particular `QubitSet` and is represented by a `PulseSequence`. Default: None. quiet (bool): Sets the verbosity of the logger to low and does not report queue @@ -190,6 +189,7 @@ def create( if tags is not None: create_task_kwargs.update({"tags": tags}) inputs = inputs or {} + gate_definitions = gate_definitions or {} if reservation_arn: create_task_kwargs.update( @@ -561,7 +561,7 @@ def _create_internal( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -577,7 +577,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], # Not currently used for OpenQasmProgram _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -600,7 +600,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -639,7 +639,7 @@ def _( _device_parameters: Union[dict, BraketSchemaBase], _disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -657,7 +657,7 @@ def _( device_parameters: Union[dict, BraketSchemaBase], disable_qubit_rewiring: bool, inputs: dict[str, float], - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], *args, **kwargs, ) -> AwsQuantumTask: @@ -678,7 +678,7 @@ def _( if ( disable_qubit_rewiring or Instruction(StartVerbatimBox()) in circuit.instructions - or gate_definitions is not None + or gate_definitions or any(isinstance(instruction.operator, PulseGate) for instruction in circuit.instructions) ): qubit_reference_type = QubitReferenceType.PHYSICAL diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index a02dfa6d6..ed3430274 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -23,8 +23,11 @@ from braket.aws.aws_quantum_task import AwsQuantumTask from braket.aws.aws_session import AwsSession from braket.circuits import Circuit +from braket.circuits.gate import Gate from braket.ir.blackbird import Program as BlackbirdProgram from braket.ir.openqasm import Program as OpenQasmProgram +from braket.pulse.pulse_sequence import PulseSequence +from braket.registers.qubit_set import QubitSet from braket.tasks.quantum_task_batch import QuantumTaskBatch @@ -61,6 +64,13 @@ def __init__( poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Union[dict[str, float], list[dict[str, float]]] | None = None, + gate_definitions: ( + Union[ + dict[tuple[Gate, QubitSet], PulseSequence], + list[dict[tuple[Gate, QubitSet], PulseSequence]], + ] + | None + ) = None, reservation_arn: str | None = None, *aws_quantum_task_args: Any, **aws_quantum_task_kwargs: Any, @@ -92,6 +102,9 @@ def __init__( inputs (Union[dict[str, float], list[dict[str, float]]] | None): Inputs to be passed along with the IR. If the IR supports inputs, the inputs will be updated with this value. Default: {}. + gate_definitions (Union[dict[tuple[Gate, QubitSet], PulseSequence], list[dict[tuple[Gate, QubitSet], PulseSequence]]] | None): # noqa: E501 + User-defined gate calibration. The calibration is defined for a particular `Gate` on a + particular `QubitSet` and is represented by a `PulseSequence`. Default: None. reservation_arn (str | None): The reservation ARN provided by Braket Direct to reserve exclusive usage for the device to run the quantum task on. Note: If you are creating tasks in a job that itself was created reservation ARN, @@ -111,6 +124,7 @@ def __init__( poll_timeout_seconds, poll_interval_seconds, inputs, + gate_definitions, reservation_arn, *aws_quantum_task_args, **aws_quantum_task_kwargs, @@ -134,7 +148,7 @@ def __init__( self._aws_quantum_task_kwargs = aws_quantum_task_kwargs @staticmethod - def _tasks_and_inputs( + def _tasks_inputs_gatedefs( task_specifications: Union[ Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], list[ @@ -144,45 +158,55 @@ def _tasks_and_inputs( ], ], inputs: Union[dict[str, float], list[dict[str, float]]] = None, + gate_definitions: Union[ + dict[tuple[Gate, QubitSet], PulseSequence], + list[dict[tuple[Gate, QubitSet], PulseSequence]], + ] = None, ) -> list[ tuple[ Union[Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation], dict[str, float], + dict[tuple[Gate, QubitSet], PulseSequence], ] ]: inputs = inputs or {} - - max_inputs_tasks = 1 - single_task = isinstance( - task_specifications, - (Circuit, Problem, OpenQasmProgram, BlackbirdProgram, AnalogHamiltonianSimulation), - ) - single_input = isinstance(inputs, dict) - - max_inputs_tasks = ( - max(max_inputs_tasks, len(task_specifications)) if not single_task else max_inputs_tasks - ) - max_inputs_tasks = ( - max(max_inputs_tasks, len(inputs)) if not single_input else max_inputs_tasks + gate_definitions = gate_definitions or {} + + single_task_type = ( + Circuit, + Problem, + OpenQasmProgram, + BlackbirdProgram, + AnalogHamiltonianSimulation, ) + single_input_type = dict + single_gate_definitions_type = dict - if not single_task and not single_input: - if len(task_specifications) != len(inputs): - raise ValueError("Multiple inputs and task specifications must be equal in number.") - if single_task: - task_specifications = repeat(task_specifications, times=max_inputs_tasks) + args = [task_specifications, inputs, gate_definitions] + single_arg_types = [single_task_type, single_input_type, single_gate_definitions_type] - if single_input: - inputs = repeat(inputs, times=max_inputs_tasks) + batch_length = 1 + arg_lengths = [] + for arg, single_arg_type in zip(args, single_arg_types): + arg_length = 1 if isinstance(arg, single_arg_type) else len(arg) + arg_lengths.append(arg_length) - tasks_and_inputs = zip(task_specifications, inputs) + if arg_length != 1: + if batch_length != 1 and arg_length != batch_length: + raise ValueError( + "Multiple inputs, task specifications and gate definitions must " + "be equal in length." + ) + else: + batch_length = arg_length - if single_task and single_input: - tasks_and_inputs = list(tasks_and_inputs) + for i, arg_length in enumerate(arg_lengths): + if arg_length == 1: + args[i] = repeat(args[i], batch_length) - tasks_and_inputs = list(tasks_and_inputs) + tasks_inputs_definitions = list(zip(*args)) - for task_specification, input_map in tasks_and_inputs: + for task_specification, input_map, _gate_definitions in tasks_inputs_definitions: if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} unbounded_parameters = param_names - set(input_map.keys()) @@ -192,7 +216,7 @@ def _tasks_and_inputs( f"{unbounded_parameters}" ) - return tasks_and_inputs + return tasks_inputs_definitions @staticmethod def _execute( @@ -213,13 +237,22 @@ def _execute( poll_timeout_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_TIMEOUT, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: Union[dict[str, float], list[dict[str, float]]] = None, + gate_definitions: ( + Union[ + dict[tuple[Gate, QubitSet], PulseSequence], + list[dict[tuple[Gate, QubitSet], PulseSequence]], + ] + | None + ) = None, reservation_arn: str | None = None, *args, **kwargs, ) -> list[AwsQuantumTask]: - tasks_and_inputs = AwsQuantumTaskBatch._tasks_and_inputs(task_specifications, inputs) + tasks_inputs_gatedefs = AwsQuantumTaskBatch._tasks_inputs_gatedefs( + task_specifications, inputs, gate_definitions + ) max_threads = min(max_parallel, max_workers) - remaining = [0 for _ in tasks_and_inputs] + remaining = [0 for _ in tasks_inputs_gatedefs] try: with ThreadPoolExecutor(max_workers=max_threads) as executor: task_futures = [ @@ -234,11 +267,12 @@ def _execute( poll_timeout_seconds=poll_timeout_seconds, poll_interval_seconds=poll_interval_seconds, inputs=input_map, + gate_definitions=gatedefs, reservation_arn=reservation_arn, *args, **kwargs, ) - for task, input_map in tasks_and_inputs + for task, input_map, gatedefs in tasks_inputs_gatedefs ] except KeyboardInterrupt: # If an exception is thrown before the thread pool has finished, @@ -266,6 +300,7 @@ def _create_task( shots: int, poll_interval_seconds: float = AwsQuantumTask.DEFAULT_RESULTS_POLL_INTERVAL, inputs: dict[str, float] = None, + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] | None = None, reservation_arn: str | None = None, *args, **kwargs, @@ -278,6 +313,7 @@ def _create_task( shots, poll_interval_seconds=poll_interval_seconds, inputs=inputs, + gate_definitions=gate_definitions, reservation_arn=reservation_arn, *args, **kwargs, diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 36f0e68fb..3f4918a1f 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -1125,6 +1125,7 @@ def to_ir( ValueError: If the supplied `ir_type` is not supported, or if the supplied serialization properties don't correspond to the `ir_type`. """ + gate_definitions = gate_definitions or {} if ir_type == IRType.JAQCD: return self._to_jaqcd() elif ir_type == IRType.OPENQASM: @@ -1137,7 +1138,7 @@ def to_ir( ) return self._to_openqasm( serialization_properties or OpenQASMSerializationProperties(), - gate_definitions.copy() if gate_definitions is not None else None, + gate_definitions.copy(), ) else: raise ValueError(f"Supplied ir_type {ir_type} is not supported.") @@ -1185,7 +1186,7 @@ def _to_jaqcd(self) -> JaqcdProgram: def _to_openqasm( self, serialization_properties: OpenQASMSerializationProperties, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], ) -> OpenQasmProgram: ir_instructions = self._create_openqasm_header(serialization_properties, gate_definitions) openqasm_ir_type = IRType.OPENQASM @@ -1222,7 +1223,7 @@ def _to_openqasm( def _create_openqasm_header( self, serialization_properties: OpenQASMSerializationProperties, - gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]], + gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], ) -> list[str]: ir_instructions = ["OPENQASM 3.0;"] frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) @@ -1244,7 +1245,7 @@ def _create_openqasm_header( ir_instructions.append(frame_wf_declarations) return ir_instructions - def _validate_gate_calbrations_uniqueness( + def _validate_gate_calibrations_uniqueness( self, gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence], frames: dict[str, Frame], @@ -1277,43 +1278,41 @@ def _generate_frame_wf_defcal_declarations( frames, waveforms = self._get_frames_waveforms_from_instrs(gate_definitions) - if gate_definitions is not None: - self._validate_gate_calbrations_uniqueness(gate_definitions, frames, waveforms) + self._validate_gate_calibrations_uniqueness(gate_definitions, frames, waveforms) # Declare the frames and waveforms across all pulse sequences declarable_frames = [f for f in frames.values() if not f.is_predefined] - if declarable_frames or waveforms or gate_definitions is not None: + if declarable_frames or waveforms or gate_definitions: frame_wf_to_declare = [f._to_oqpy_expression() for f in declarable_frames] frame_wf_to_declare += [wf._to_oqpy_expression() for wf in waveforms.values()] program.declare(frame_wf_to_declare, encal=True) - if gate_definitions is not None: - for key, calibration in gate_definitions.items(): - gate, qubits = key - - # Ignoring parametric gates - # Corresponding defcals with fixed arguments have been added - # in _get_frames_waveforms_from_instrs - if isinstance(gate, Parameterizable) and any( - not isinstance(parameter, (float, int, complex)) - for parameter in gate.parameters - ): - continue - - gate_name = gate._qasm_name - arguments = gate.parameters if isinstance(gate, Parameterizable) else [] - - for param in calibration.parameters: - self._parameters.add(param) - arguments = [ - param._to_oqpy_expression() if isinstance(param, FreeParameter) else param - for param in arguments - ] - - with oqpy.defcal( - program, [oqpy.PhysicalQubits[int(k)] for k in qubits], gate_name, arguments - ): - program += calibration._program + for key, calibration in gate_definitions.items(): + gate, qubits = key + + # Ignoring parametric gates + # Corresponding defcals with fixed arguments have been added + # in _get_frames_waveforms_from_instrs + if isinstance(gate, Parameterizable) and any( + not isinstance(parameter, (float, int, complex)) + for parameter in gate.parameters + ): + continue + + gate_name = gate._qasm_name + arguments = gate.parameters if isinstance(gate, Parameterizable) else [] + + for param in calibration.parameters: + self._parameters.add(param) + arguments = [ + param._to_oqpy_expression() if isinstance(param, FreeParameter) else param + for param in arguments + ] + + with oqpy.defcal( + program, [oqpy.PhysicalQubits[int(k)] for k in qubits], gate_name, arguments + ): + program += calibration._program ast = program.to_ast(encal=False, include_externs=False) return ast_to_qasm(ast) @@ -1321,7 +1320,7 @@ def _generate_frame_wf_defcal_declarations( return None def _get_frames_waveforms_from_instrs( - self, gate_definitions: Optional[dict[tuple[Gate, QubitSet], PulseSequence]] + self, gate_definitions: dict[tuple[Gate, QubitSet], PulseSequence] ) -> tuple[dict[str, Frame], dict[str, Waveform]]: from braket.circuits.gates import PulseGate @@ -1336,7 +1335,7 @@ def _get_frames_waveforms_from_instrs( _validate_uniqueness(waveforms, waveform) waveforms[waveform.id] = waveform # this will change with full parametric calibration support - elif isinstance(instruction.operator, Parameterizable) and gate_definitions is not None: + elif isinstance(instruction.operator, Parameterizable): fixed_argument_calibrations = self._add_fixed_argument_calibrations( gate_definitions, instruction ) diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index cac1c1536..a85ca6eb9 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -1138,7 +1138,7 @@ def test_run_param_circuit_with_reservation_arn_batch_task( 43200, 0.25, inputs, - None, + {}, reservation_arn="arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", ) @@ -1170,6 +1170,7 @@ def test_run_param_circuit_with_inputs_batch_task( 43200, 0.25, inputs, + {}, ) @@ -1303,7 +1304,9 @@ def test_batch_circuit_with_task_and_input_mismatch( inputs = [{"beta": 0.2}, {"gamma": 0.1}, {"theta": 0.2}] circ_1 = Circuit().ry(angle=3, target=0) task_specifications = [[circ_1, single_circuit_input], openqasm_program] - wrong_number_of_inputs = "Multiple inputs and task specifications must " "be equal in number." + wrong_number_of_inputs = ( + "Multiple inputs, task specifications and gate definitions must be equal in length." + ) with pytest.raises(ValueError, match=wrong_number_of_inputs): _run_batch_and_assert( @@ -1318,6 +1321,7 @@ def test_batch_circuit_with_task_and_input_mismatch( 43200, 0.25, inputs, + {}, ) @@ -1494,7 +1498,7 @@ def test_run_with_positional_args_and_kwargs( 86400, 0.25, {}, - ["foo"], + {}, "arn:aws:braket:us-west-2:123456789123:reservation/a1b123cd-45e6-789f-gh01-i234567jk8l9", None, {"bar": 1, "baz": 2}, @@ -1534,6 +1538,7 @@ def test_run_batch_no_extra( 43200, 0.25, {}, + {}, ) @@ -1560,6 +1565,7 @@ def test_run_batch_with_shots( 43200, 0.25, {}, + {}, ) @@ -1586,6 +1592,7 @@ def test_run_batch_with_max_parallel_and_kwargs( 43200, 0.25, inputs={"theta": 0.2}, + gate_definitions={}, extra_kwargs={"bar": 1, "baz": 2}, ) From 314bf5916f4de6fcc12943a52a1213118304d6d4 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 21 Mar 2024 09:43:06 -0700 Subject: [PATCH 34/98] fix: batch tasking passing lists to single tasks (#917) --- src/braket/aws/aws_quantum_task_batch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index ed3430274..4d0d06b53 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -201,7 +201,7 @@ def _tasks_inputs_gatedefs( batch_length = arg_length for i, arg_length in enumerate(arg_lengths): - if arg_length == 1: + if isinstance(args[i], (dict, single_task_type)): args[i] = repeat(args[i], batch_length) tasks_inputs_definitions = list(zip(*args)) From 1751ec96afb7bd25056d8bc6538a4d358b945125 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Mar 2024 19:32:43 +0000 Subject: [PATCH 35/98] prepare release v1.74.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43cb66841..83ac54ffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.74.0 (2024-03-21) + +### Features + + * Allow sets of calibrations in batches + +### Bug Fixes and Other Changes + + * batch tasking passing lists to single tasks + ## v1.73.3 (2024-03-18) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 55053a2de..dc2e7fdf3 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.73.4.dev0" +__version__ = "1.74.0" From 661529a0c003266794ab47a7812d15f315537809 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 21 Mar 2024 19:32:43 +0000 Subject: [PATCH 36/98] update development version to v1.74.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index dc2e7fdf3..9c443c490 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.74.0" +__version__ = "1.74.1.dev0" From 8618fcb4a23a1b37b09d99264c0e953792117ae9 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 27 Mar 2024 14:20:01 -0700 Subject: [PATCH 37/98] fix: temporarily pin the schemas version (#924) --- setup.py | 2 +- tox.ini | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 16f96bd31..6057b05c1 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.20.2", + "amazon-braket-schemas==1.20.2", "amazon-braket-default-simulator>=1.19.1", "oqpy~=0.3.5", "setuptools", diff --git a/tox.ini b/tox.ini index 98a9b30e3..7e758d02d 100644 --- a/tox.ini +++ b/tox.ini @@ -129,5 +129,4 @@ commands = [test-deps] deps = # If you need to test on a certain branch, add @ after .git - git+https://github.com/amazon-braket/amazon-braket-schemas-python.git git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git From baf2ff23f76a831b279c277e4eb280579894d6ff Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 27 Mar 2024 21:33:12 +0000 Subject: [PATCH 38/98] prepare release v1.74.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ac54ffa..0e721a98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.74.1 (2024-03-27) + +### Bug Fixes and Other Changes + + * temporarily pin the schemas version + ## v1.74.0 (2024-03-21) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9c443c490..59f3b7900 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.74.1.dev0" +__version__ = "1.74.1" From 182b923259b2ca5824fee49da70aad1f487daaae Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 27 Mar 2024 21:33:12 +0000 Subject: [PATCH 39/98] update development version to v1.74.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 59f3b7900..71a218161 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.74.1" +__version__ = "1.74.2.dev0" From cfbdd50862c481d7e8c36b75f9828f2afe7eb2af Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:21:52 -0700 Subject: [PATCH 40/98] fix: change schemas constraint (#927) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6057b05c1..16f96bd31 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas==1.20.2", + "amazon-braket-schemas>=1.20.2", "amazon-braket-default-simulator>=1.19.1", "oqpy~=0.3.5", "setuptools", From c42d46e9413ac5da12d77020df46ff4a638c3956 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 27 Mar 2024 16:48:30 -0700 Subject: [PATCH 41/98] feat: upgrade to pydantic 2.x (#926) * Update dependent-tests.yml --------- Co-authored-by: Abe Coull --- .github/workflows/dependent-tests.yml | 2 +- model.tar.gz | Bin 0 -> 336 bytes setup.py | 2 +- .../braket/circuits/test_angled_gate.py | 2 +- .../braket/devices/test_local_simulator.py | 2 +- tox.ini | 1 + 6 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 model.tar.gz diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index aca79d599..fa49d9b08 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -18,7 +18,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.9", "3.10", "3.11"] dependent: - - amazon-braket-pennylane-plugin-python + - amazon-braket-schemas-python steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/model.tar.gz b/model.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..93bf6a4a03f7d08314601e2907a704651eb0b07a GIT binary patch literal 336 zcmV-W0k8faiwFP!000001MHQ}OT#c2#(Va!2svx^rcE0sc<_@AJcxpL8(AB)b4^B) z%4F<+H{HjjuuWKXsQq1x%3_`*Q35O_NgDPfvx=n;VBX>FXTDp6uL>oc|;iH ztQ*0R_tGt1vB_fzy6azFJY4nqPd6kr(*HrLP1T3Kf&b071ir@3{KvGOe~7{W?VZW5 zu+G2H+HI@b<^R(B&+yQQH|ZYJS6PVVLx9iF3@cGcKUmphq=$Bp2`9+JzZAK3G8=ep zA>m_$-z!zCY6Zn}FI2{Lo>s{h=3~(^)ykK>$jr~2DW$KH$_tfy0wi27yVa%;u4*+I ii(EN5b$EX0i)v|UY58M(0ssL2{{sNvw;Ch>3;+NyA)$Q$ literal 0 HcmV?d00001 diff --git a/setup.py b/setup.py index 16f96bd31..f2621de52 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.20.2", + "amazon-braket-schemas>=1.21.0", "amazon-braket-default-simulator>=1.19.1", "oqpy~=0.3.5", "setuptools", diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index 4e093e5b4..ae33d4029 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -15,7 +15,7 @@ import numpy as np import pytest -from pydantic import BaseModel +from pydantic.v1 import BaseModel from braket.circuits import AngledGate, FreeParameter, FreeParameterExpression, Gate from braket.circuits.angled_gate import DoubleAngledGate, TripleAngledGate diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index 4877a0b8f..f284b5a6f 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -18,7 +18,7 @@ from unittest.mock import Mock, patch import pytest -from pydantic import create_model # This is temporary for defining properties below +from pydantic.v1 import create_model # This is temporary for defining properties below import braket.ir as ir from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation diff --git a/tox.ini b/tox.ini index 7e758d02d..98a9b30e3 100644 --- a/tox.ini +++ b/tox.ini @@ -129,4 +129,5 @@ commands = [test-deps] deps = # If you need to test on a certain branch, add @ after .git + git+https://github.com/amazon-braket/amazon-braket-schemas-python.git git+https://github.com/amazon-braket/amazon-braket-default-simulator-python.git From a52654608374ee4da1d95b44d46f8c0541d31631 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 28 Mar 2024 00:19:15 +0000 Subject: [PATCH 42/98] prepare release v1.75.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e721a98b..b8a9aa145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.75.0 (2024-03-28) + +### Features + + * upgrade to pydantic 2.x + +### Bug Fixes and Other Changes + + * change schemas constraint + ## v1.74.1 (2024-03-27) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 71a218161..a2711b93e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.74.2.dev0" +__version__ = "1.75.0" From 5884b1f9debbd9e6ee5cd130858b00323b766a19 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 28 Mar 2024 00:19:15 +0000 Subject: [PATCH 43/98] update development version to v1.75.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index a2711b93e..9c4e62904 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.75.0" +__version__ = "1.75.1.dev0" From d6209eeb38f9e4d9efad5f7c1c7dd9a37a5e349f Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 28 Mar 2024 10:50:45 -0700 Subject: [PATCH 44/98] fix: restore the dependent test back to pennylane (#928) --- .github/workflows/dependent-tests.yml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dependent-tests.yml b/.github/workflows/dependent-tests.yml index fa49d9b08..aca79d599 100644 --- a/.github/workflows/dependent-tests.yml +++ b/.github/workflows/dependent-tests.yml @@ -18,7 +18,7 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] python-version: ["3.9", "3.10", "3.11"] dependent: - - amazon-braket-schemas-python + - amazon-braket-pennylane-plugin-python steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/setup.py b/setup.py index f2621de52..6c0a9e984 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.0", - "amazon-braket-default-simulator>=1.19.1", + "amazon-braket-default-simulator>=1.21.2", "oqpy~=0.3.5", "setuptools", "backoff", From ecf30b6d7e958f446346233d487d699a32267b3e Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:59:39 -0700 Subject: [PATCH 45/98] doc: fix GPI2 gate matrix representation (#919) --- src/braket/circuits/gates.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index df17c03c3..b93b2dfa7 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -3301,7 +3301,7 @@ class GPi2(AngledGate): Unitary matrix: - .. math:: \mathtt{GPi2}(\phi) = \begin{bmatrix} + .. math:: \mathtt{GPi2}(\phi) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -i e^{-i \phi} \\ -i e^{i \phi} & 1 \end{bmatrix}. @@ -3351,7 +3351,7 @@ def gpi2( ) -> Iterable[Instruction]: r"""IonQ GPi2 gate. - .. math:: \mathtt{GPi2}(\phi) = \begin{bmatrix} + .. math:: \mathtt{GPi2}(\phi) = \frac{1}{\sqrt{2}} \begin{bmatrix} 1 & -i e^{-i \phi} \\ -i e^{i \phi} & 1 \end{bmatrix}. From e98aead1a67c2e475e7f1be7c49a0431a2faab2e Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Mon, 1 Apr 2024 08:28:56 -0700 Subject: [PATCH 46/98] feature: add support for OpenQASM measure on a subset of qubits (#904) * add support for openqasm measure on a subset of qubits * add tests to increase test coverage * Update circuit diagram to new format * Increase test converage * add tests for edge cases * infra: bump default simulator dependency to 1.21.0 * add integ tests * remove unecessary check on the target list * add tests for result type and verbatim edge cases * Update error messages, add diagram tests, and update measure to measure all qubits when no targets provided * fix linter type hint error in docstring * add measure support to braket_program_context and added tests for from_ir * fix type hint documentation error * Fix linter errors * Apply suggestions from code review Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> * add error if qubit already measured, update target_qubits to QubitSetInput * fix linter error * remove qubit_count and ascii_symbols from Measure * Apply suggestions from code review Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> * remove error if measure targets are outside circuit * update error message casing to be consistent, change ascii_symbol type, and combined error message for qubits already measured * add a test for measuring qubits not on the circuit * allow operations after a measure if the target is not measured * remove hanging prints * fix missed case inconsistency * fix missed case inconsistency * add comments for the different cases if a qubit is already measured * updated test to check for edge case --------- Co-authored-by: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> --- src/braket/circuits/braket_program_context.py | 10 + src/braket/circuits/circuit.py | 151 ++++++++- src/braket/circuits/measure.py | 99 ++++++ src/braket/circuits/moments.py | 11 + .../text_circuit_diagram_utils.py | 5 +- .../tasks/gate_model_quantum_task_result.py | 5 - test/integ_tests/test_measure.py | 85 +++++ .../circuits/test_ascii_circuit_diagram.py | 63 ++++ .../braket/circuits/test_circuit.py | 320 ++++++++++++++++-- .../braket/circuits/test_measure.py | 100 ++++++ .../circuits/test_unicode_circuit_diagram.py | 78 +++++ .../test_gate_model_quantum_task_result.py | 15 - 12 files changed, 881 insertions(+), 61 deletions(-) create mode 100644 src/braket/circuits/measure.py create mode 100644 test/integ_tests/test_measure.py create mode 100644 test/unit_tests/braket/circuits/test_measure.py diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 46d6e3b0b..a17864ed4 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -18,6 +18,7 @@ from braket.circuits import Circuit, Instruction from braket.circuits.gates import Unitary +from braket.circuits.measure import Measure from braket.circuits.noises import Kraus from braket.circuits.translations import ( BRAKET_GATES, @@ -159,3 +160,12 @@ def handle_parameter_value( return evaluated_value return FreeParameterExpression(evaluated_value) return value + + def add_measure(self, target: tuple[int]) -> None: + """Add a measure instruction to the circuit + + Args: + target (tuple[int]): the target qubits to be measured. + """ + instruction = Instruction(Measure(), list(target)) + self._circuit.add_instruction(instruction) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 3f4918a1f..61fcd38bb 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -26,6 +26,7 @@ from braket.circuits.free_parameter_expression import FreeParameterExpression from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.measure import Measure from braket.circuits.moments import Moments, MomentType from braket.circuits.noise import Noise from braket.circuits.noise_helpers import ( @@ -148,6 +149,7 @@ def __init__(self, addable: AddableTypes | None = None, *args, **kwargs): self._parameters = set() self._observables_simultaneously_measurable = True self._has_compiler_directives = False + self._measure_targets = None if addable is not None: self.add(addable, *args, **kwargs) @@ -273,6 +275,7 @@ def add_result_type( Raises: TypeError: If both `target_mapping` and `target` are supplied. + ValueError: If a meaure instruction exists on the current circuit. Examples: >>> result_type = ResultType.Probability(target=[0, 1]) @@ -298,6 +301,12 @@ def add_result_type( if target_mapping and target is not None: raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") + if self._measure_targets: + raise ValueError( + "cannot add a result type to a circuit which already contains a " + "measure instruction." + ) + if not target_mapping and not target: # Nothing has been supplied, add result_type result_type_to_add = result_type @@ -407,6 +416,46 @@ def _add_to_qubit_observable_set(self, result_type: ResultType) -> None: if isinstance(result_type, ObservableResultType) and result_type.target: self._qubit_observable_set.update(result_type.target) + def _check_if_qubit_measured( + self, + instruction: Instruction, + target: QubitSetInput | None = None, + target_mapping: dict[QubitInput, QubitInput] | None = None, + ) -> None: + """Checks if the target qubits are measured. If the qubit is already measured + the instruction will not be added to the Circuit. + + Args: + instruction (Instruction): `Instruction` to add into `self`. + target (QubitSetInput | None): Target qubits for the + `instruction`. If a single qubit gate, an instruction is created for every index + in `target`. + Default = `None`. + target_mapping (dict[QubitInput, QubitInput] | None): A dictionary of + qubit mappings to apply to the `instruction.target`. Key is the qubit in + `instruction.target` and the value is what the key will be changed to. + Default = `None`. + + Raises: + ValueError: If adding a gate or noise operation after a measure instruction. + """ + if ( + # check if there is a measure instruction on the target qubit + target + and target in self._measure_targets + # check if there is a measure instruction on any qubits in the target_mapping + or (target_mapping and any(targ in self._measure_targets for targ in target_mapping)) + # If no target or target_mapping is supplied, check if there is a measure + # instruction on the current instructions target qubit + or ( + instruction.target + and any(targ in self._measure_targets for targ in instruction.target) + ) + ): + raise ValueError( + "cannot add a gate or noise operation on a qubit after a measure instruction." + ) + def add_instruction( self, instruction: Instruction, @@ -431,6 +480,7 @@ def add_instruction( Raises: TypeError: If both `target_mapping` and `target` are supplied. + ValueError: If adding a gate or noise after a measure instruction. Examples: >>> instr = Instruction(Gate.CNot(), [0, 1]) @@ -458,6 +508,10 @@ def add_instruction( if target_mapping and target is not None: raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") + # Check if there is a measure instruction on the circuit + if not isinstance(instruction.operator, Measure) and self._measure_targets: + self._check_if_qubit_measured(instruction, target, target_mapping) + if not target_mapping and not target: # Nothing has been supplied, add instruction instructions_to_add = [instruction] @@ -635,6 +689,9 @@ def add_verbatim_box( if verbatim_circuit.result_types: raise ValueError("Verbatim subcircuit is not measured and cannot have result types") + if verbatim_circuit._measure_targets: + raise ValueError("cannot measure a subcircuit inside a verbatim box.") + if verbatim_circuit.instructions: self.add_instruction(Instruction(compiler_directives.StartVerbatimBox())) for instruction in verbatim_circuit.instructions: @@ -643,6 +700,90 @@ def add_verbatim_box( self._has_compiler_directives = True return self + def _add_measure(self, target_qubits: QubitSetInput) -> None: + """Adds a measure instruction to the the circuit + + Args: + target_qubits (QubitSetInput): target qubits to measure. + """ + for idx, target in enumerate(target_qubits): + num_qubits_measured = ( + len(self._measure_targets) + if self._measure_targets and len(target_qubits) == 1 + else 0 + ) + self.add_instruction( + Instruction( + operator=Measure(index=idx + num_qubits_measured), + target=target, + ) + ) + if self._measure_targets: + self._measure_targets.append(target) + else: + self._measure_targets = [target] + + def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: + """ + Add a `measure` operator to `self` ensuring only the target qubits are measured. + + Args: + target_qubits (QubitSetInput | None): target qubits to measure. + Default=None + + Returns: + Circuit: self + + Raises: + IndexError: If `self` has no qubits. + IndexError: If target qubits are not within the range of the current circuit. + ValueError: If the current circuit contains any result types. + ValueError: If the target qubit is already measured. + + Examples: + >>> circ = Circuit.h(0).cnot(0, 1).measure([0]) + >>> circ.print(list(circ.instructions)) + [Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)]), + Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), + Qubit(1)]), + Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(2)]), + Instruction('operator': Measure, 'target': QubitSet([Qubit(0)])] + """ + # check whether measuring an empty circuit + if not self.qubits: + raise IndexError("cannot measure an empty circuit.") + + if isinstance(target_qubits, int): + target_qubits = [target_qubits] + + # Check if result types are added on the circuit + if self.result_types: + raise ValueError("a circuit cannot contain both measure instructions and result types.") + + if target_qubits: + # Check if the target_qubits are already measured + if self._measure_targets and all( + target in self._measure_targets for target in target_qubits + ): + intersection = set(target_qubits) & set(self._measure_targets) + raise ValueError( + f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " + "more than once." + ) + self._add_measure(target_qubits=target_qubits) + else: + # Check if any qubits are already measured + if self._measure_targets: + intersection = set(self.qubits) & set(self._measure_targets) + raise ValueError( + f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " + "more than once." + ) + # Measure all the qubits + self._add_measure(target_qubits=self.qubits) + + return self + def apply_gate_noise( self, noise: Union[type[Noise], Iterable[type[Noise]]], @@ -1208,7 +1349,8 @@ def _to_openqasm( for result_type in self.result_types ] ) - else: + # measure all the qubits if a measure instruction is not provided + elif self._measure_targets is None: qubits = ( sorted(self.qubits) if serialization_properties.qubit_reference_type == QubitReferenceType.VIRTUAL @@ -1230,7 +1372,12 @@ def _create_openqasm_header( for parameter in self.parameters: ir_instructions.append(f"input float {parameter};") if not self.result_types: - ir_instructions.append(f"bit[{self.qubit_count}] b;") + bit_count = ( + len(self._measure_targets) + if self._measure_targets is not None + else self.qubit_count + ) + ir_instructions.append(f"bit[{bit_count}] b;") if serialization_properties.qubit_reference_type == QubitReferenceType.VIRTUAL: total_qubits = max(self.qubits).real + 1 diff --git a/src/braket/circuits/measure.py b/src/braket/circuits/measure.py new file mode 100644 index 000000000..d31555ba9 --- /dev/null +++ b/src/braket/circuits/measure.py @@ -0,0 +1,99 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from __future__ import annotations + +from typing import Any + +from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + SerializationProperties, +) +from braket.registers.qubit_set import QubitSet + + +class Measure(QuantumOperator): + """Class `Measure` represents a measure operation on targeted qubits""" + + def __init__(self, **kwargs): + """Inits a `Measure`. + + Raises: + ValueError: `qubit_count` is less than 1, `ascii_symbols` are `None`, or + `ascii_symbols` length != `qubit_count` + """ + super().__init__(qubit_count=1, ascii_symbols=["M"]) + self._target_index = kwargs.get("index") + + @property + def ascii_symbols(self) -> tuple[str]: + """tuple[str]: Returns the ascii symbols for the measure.""" + return self._ascii_symbols + + def to_ir( + self, + target: QubitSet | None = None, + ir_type: IRType = IRType.OPENQASM, + serialization_properties: SerializationProperties | None = None, + **kwargs, + ) -> Any: + """Returns IR object of the measure operator. + + Args: + target (QubitSet | None): target qubit(s). Defaults to None + ir_type(IRType) : The IRType to use for converting the measure object to its + IR representation. Defaults to IRType.OpenQASM. + serialization_properties (SerializationProperties | None): The serialization properties + to use while serializing the object to the IR representation. The serialization + properties supplied must correspond to the supplied `ir_type`. Defaults to None. + + Returns: + Any: IR object of the measure operator. + + Raises: + ValueError: If the supplied `ir_type` is not supported. + """ + if ir_type == IRType.JAQCD: + return self._to_jaqcd() + elif ir_type == IRType.OPENQASM: + return self._to_openqasm( + target, serialization_properties or OpenQASMSerializationProperties() ** kwargs + ) + else: + raise ValueError(f"supplied ir_type {ir_type} is not supported.") + + def _to_jaqcd(self) -> Any: + """Returns the JAQCD representation of the measure.""" + raise NotImplementedError("measure instructions are not supported with JAQCD.") + + def _to_openqasm( + self, target: QubitSet, serialization_properties: OpenQASMSerializationProperties + ) -> str: + """Returns the openqasm string representation of the measure.""" + target_qubits = [serialization_properties.format_target(int(qubit)) for qubit in target] + instructions = [] + for idx, qubit in enumerate(target_qubits): + bit_index = ( + self._target_index if self._target_index and len(target_qubits) == 1 else idx + ) + instructions.append(f"b[{bit_index}] = measure {qubit};") + + return "\n".join(instructions) + + def __eq__(self, other: Measure): + return isinstance(other, Measure) + + def __repr__(self): + return self.name diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 64a3556c1..031c2f5e5 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -21,6 +21,7 @@ from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.measure import Measure from braket.circuits.noise import Noise from braket.registers.qubit import Qubit from braket.registers.qubit_set import QubitSet @@ -34,6 +35,7 @@ class MomentType(str, Enum): INITIALIZATION_NOISE: a initialization noise channel READOUT_NOISE: a readout noise channel COMPILER_DIRECTIVE: an instruction to the compiler, external to the quantum program itself + MEASURE: a measurement """ GATE = "gate" @@ -43,6 +45,7 @@ class MomentType(str, Enum): READOUT_NOISE = "readout_noise" COMPILER_DIRECTIVE = "compiler_directive" GLOBAL_PHASE = "global_phase" + MEASURE = "measure" class MomentsKey(NamedTuple): @@ -191,6 +194,14 @@ def _add(self, instruction: Instruction, noise_index: int = 0) -> None: self._number_gphase_in_current_moment, ) self._moments[key] = instruction + elif isinstance(operator, Measure): + qubit_range = instruction.target.union(instruction.control) + time = self._get_qubit_times(self._max_times.keys()) + 1 + self._moments[MomentsKey(time, qubit_range, MomentType.MEASURE, noise_index)] = ( + instruction + ) + self._qubits.update(qubit_range) + self._depth = max(self._depth, time + 1) else: qubit_range = instruction.target.union(instruction.control) time = self._update_qubit_times(qubit_range) diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py index 9b85c30e1..83763a9ae 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -20,6 +20,7 @@ from braket.circuits.compiler_directive import CompilerDirective from braket.circuits.gate import Gate from braket.circuits.instruction import Instruction +from braket.circuits.measure import Measure from braket.circuits.moments import MomentType from braket.circuits.noise import Noise from braket.circuits.result_type import ResultType @@ -129,9 +130,9 @@ def _group_items( """ groupings = [] for item in items: - # Can only print Gate and Noise operators for instructions at the moment + # Can only print Gate, Noise and Measure operators for instructions at the moment if isinstance(item, Instruction) and not isinstance( - item.operator, (Gate, Noise, CompilerDirective) + item.operator, (Gate, Noise, CompilerDirective, Measure) ): continue diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index dec914ba7..b64d4e009 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -286,11 +286,6 @@ def _from_object_internal_computational_basis_sampling( " the result obj", ) measured_qubits = result.measuredQubits - if len(measured_qubits) != measurements.shape[1]: - raise ValueError( - f"Measured qubits {measured_qubits} is not equivalent to number of qubits " - + f"{measurements.shape[1]} in measurements" - ) if result.resultTypes: # Jaqcd does not return anything in the resultTypes schema field since the # result types are easily parsable from the IR. However, an OpenQASM program diff --git a/test/integ_tests/test_measure.py b/test/integ_tests/test_measure.py new file mode 100644 index 000000000..b7fef275b --- /dev/null +++ b/test/integ_tests/test_measure.py @@ -0,0 +1,85 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +import re + +import pytest +from botocore.exceptions import ClientError + +from braket.aws.aws_device import AwsDevice +from braket.circuits.circuit import Circuit +from braket.devices import LocalSimulator + +DEVICE = LocalSimulator() +SHOTS = 8000 + +IONQ_ARN = "arn:aws:braket:us-east-1::device/qpu/ionq/Harmony" +SIMULATOR_ARN = "arn:aws:braket:::device/quantum-simulator/amazon/sv1" +OQC_ARN = "arn:aws:braket:eu-west-2::device/qpu/oqc/Lucy" + + +@pytest.mark.parametrize("arn", [(IONQ_ARN), (SIMULATOR_ARN)]) +def test_unsupported_devices(arn): + device = AwsDevice(arn) + if device.status == "OFFLINE": + pytest.skip("Device offline") + + circ = Circuit().h(0).cnot(0, 1).h(2).measure([0, 1]) + error_string = re.escape( + "An error occurred (ValidationException) when calling the " + "CreateQuantumTask operation: Device requires all qubits in the program to be measured. " + "This may be caused by declaring non-contiguous qubits or measuring partial qubits" + ) + with pytest.raises(ClientError, match=error_string): + device.run(circ, shots=1000) + + +@pytest.mark.parametrize("sim", [("braket_sv"), ("braket_dm")]) +def test_measure_on_local_sim(sim): + circ = Circuit().h(0).cnot(0, 1).h(2).measure([0, 1]) + device = LocalSimulator(sim) + result = device.run(circ, SHOTS).result() + assert len(result.measurements[0]) == 2 + assert result.measured_qubits == [0, 1] + + +@pytest.mark.parametrize("arn", [(OQC_ARN)]) +def test_measure_on_supported_devices(arn): + device = AwsDevice(arn) + if not device.is_available: + pytest.skip("Device offline") + circ = Circuit().h(0).cnot(0, 1).measure([0]) + result = device.run(circ, SHOTS).result() + assert len(result.measurements[0]) == 1 + assert result.measured_qubits == [0] + + +@pytest.mark.parametrize( + "circuit, expected_measured_qubits", + [ + (Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 1, 3]), [0, 1, 3]), + (Circuit().h(0).measure(0), [0]), + ], +) +def test_measure_targets(circuit, expected_measured_qubits): + result = DEVICE.run(circuit, SHOTS).result() + assert result.measured_qubits == expected_measured_qubits + assert len(result.measurements[0]) == len(expected_measured_qubits) + + +def test_measure_with_noise(): + device = LocalSimulator("braket_dm") + circuit = Circuit().x(0).x(1).bit_flip(0, probability=0.1).measure(0) + result = device.run(circuit, SHOTS).result() + assert result.measured_qubits == [0] + assert len(result.measurements[0]) == 1 diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 88e32d92f..7724d9077 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -872,3 +872,66 @@ def __init__(self): "T : | 0 | 1 | 2 | 3 | 4 |", ) _assert_correct_diagram(circ, expected) + + +def test_measure(): + circ = Circuit().h(0).cnot(0, 1).measure([0]) + expected = ( + "T : |0|1|2|", + " ", + "q0 : -H-C-M-", + " | ", + "q1 : ---X---", + "", + "T : |0|1|2|", + ) + _assert_correct_diagram(circ, expected) + + +def test_measure_multiple_targets(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 2, 3]) + expected = ( + "T : |0|1|2|3|4|", + " ", + "q0 : -H-C-----M-", + " | ", + "q1 : ---X-C-----", + " | ", + "q2 : -----X-C-M-", + " | ", + "q3 : -------X-M-", + "", + "T : |0|1|2|3|4|", + ) + _assert_correct_diagram(circ, expected) + + +def test_measure_multiple_instructions_after(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .cnot(1, 2) + .cnot(2, 3) + .measure(0) + .measure(1) + .h(3) + .cnot(3, 4) + .measure([2, 3]) + ) + expected = ( + "T : |0|1|2|3|4|5|6|", + " ", + "q0 : -H-C-----M-----", + " | ", + "q1 : ---X-C---M-----", + " | ", + "q2 : -----X-C-----M-", + " | ", + "q3 : -------X-H-C-M-", + " | ", + "q4 : -----------X---", + "", + "T : |0|1|2|3|4|5|6|", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index ecaad12bf..9b34d6629 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -35,6 +35,8 @@ observables, ) from braket.circuits.gate_calibrations import GateCalibrations +from braket.circuits.measure import Measure +from braket.circuits.noises import BitFlip from braket.circuits.parameterizable import Parameterizable from braket.circuits.serialization import ( IRType, @@ -570,6 +572,233 @@ def test_add_verbatim_box_result_types(): ) +def test_measure(): + circ = Circuit().h(0).cnot(0, 1).measure([0]) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 0)) + ) + assert circ == expected + + +def test_measure_int(): + circ = Circuit().h(0).cnot(0, 1).measure(0) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 0)) + ) + assert circ == expected + + +def test_measure_multiple_targets(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 1, 3]) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.CNot(), [1, 2])) + .add_instruction(Instruction(Gate.CNot(), [2, 3])) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Measure(), 3)) + ) + assert circ == expected + assert circ._measure_targets == [0, 1, 3] + + +def test_measure_with_noise(): + circ = Circuit().x(0).x(1).bit_flip(0, probability=0.1).measure(0) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(BitFlip(probability=0.1), 0)) + .add_instruction(Instruction(Measure(), 0)) + ) + assert circ == expected + + +def test_measure_verbatim_box(): + circ = Circuit().add_verbatim_box(Circuit().x(0).x(1)).measure(0) + expected = ( + Circuit() + .add_instruction(Instruction(compiler_directives.StartVerbatimBox())) + .add_instruction(Instruction(Gate.X(), 0)) + .add_instruction(Instruction(Gate.X(), 1)) + .add_instruction(Instruction(compiler_directives.EndVerbatimBox())) + .add_instruction(Instruction(Measure(), 0)) + ) + expected_ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[2] q;", + "#pragma braket verbatim", + "box{", + "x q[0];", + "x q[1];", + "}", + "b[0] = measure q[0];", + ] + ), + inputs={}, + ) + assert circ == expected + assert circ.to_ir("OPENQASM") == expected_ir + + +def test_measure_in_verbatim_subcircuit(): + message = "cannot measure a subcircuit inside a verbatim box." + with pytest.raises(ValueError, match=message): + Circuit().add_verbatim_box(Circuit().x(0).x(1).measure(0)) + + +def test_measure_qubits_out_of_range(): + circ = Circuit().h(0).cnot(0, 1).measure(4) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 4)) + ) + assert circ == expected + + +def test_measure_empty_circuit(): + with pytest.raises(IndexError): + Circuit().measure() + + +def test_measure_no_target(): + circ = Circuit().h(0).cnot(0, 1).measure() + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + ) + assert circ == expected + + +def test_measure_with_result_types(): + message = "a circuit cannot contain both measure instructions and result types." + with pytest.raises(ValueError, match=message): + Circuit().h(0).sample(observable=Observable.Z(), target=0).measure(0) + + +def test_result_type_with_measure(): + message = "cannot add a result type to a circuit which already contains a measure instruction." + with pytest.raises(ValueError, match=message): + Circuit().h(0).measure(0).sample(observable=Observable.Z(), target=0) + + +def test_measure_with_multiple_measures(): + circ = Circuit().h(0).cnot(0, 1).h(2).measure([0, 1]).measure(2) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.H(), 2)) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Measure(), 2)) + ) + assert circ == expected + + +def test_measure_same_qubit_twice(): + message = "cannot measure the same qubit\\(s\\) 0 more than once." + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).measure(0).measure(1).measure(0) + + +def test_measure_empty_measure_after_measure_with_targets(): + message = "cannot measure the same qubit\\(s\\) 0, 1 more than once." + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).measure(1).measure() + + +def test_measure_gate_after(): + message = "cannot add a gate or noise operation on a qubit after a measure instruction." + with pytest.raises(ValueError, match=message): + Circuit().h(0).measure(0).h([0, 1]) + + +def test_measure_gate_after_with_target_mapping(): + message = "cannot add a gate or noise operation on a qubit after a measure instruction." + instr = Instruction(Gate.CNot(), [0, 1]) + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction( + instr, target_mapping={0: 10, 1: 11} + ) + + +def test_measure_gate_after_with_target(): + message = "cannot add a gate or noise operation on a qubit after a measure instruction." + instr = Instruction(Gate.CNot(), [0, 1]) + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction(instr, target=[10, 11]) + + +def test_measure_gate_after_measurement(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).h(2) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .add_instruction(Instruction(Gate.CNot(), [1, 2])) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Gate.H(), 2)) + ) + assert circ == expected + + +def test_to_ir_with_measure(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 2]) + expected_ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "cnot q[1], q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[2];", + ] + ), + inputs={}, + ) + assert circ.to_ir("OPENQASM") == expected_ir + + +def test_from_ir_with_measure(): + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[1] b;", + "qubit[3] q;", + "h q[0];", + "cnot q[0], q[1];", + "cnot q[1], q[2];", + "b[0] = measure q[0];", + "b[1] = measure q[2];", + ] + ), + inputs={}, + ) + expected_circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).measure(2) + assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == expected_circ + + def test_add_with_instruction_with_default(cnot_instr): circ = Circuit().add(cnot_instr) assert circ == Circuit().add_instruction(cnot_instr) @@ -1268,7 +1497,7 @@ def foo( "expected_circuit, ir", [ ( - Circuit().h(0, control=1, control_state=0), + Circuit().h(0, control=1, control_state=0).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1284,7 +1513,7 @@ def foo( ), ), ( - Circuit().cnot(target=0, control=1), + Circuit().cnot(target=0, control=1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1300,7 +1529,7 @@ def foo( ), ), ( - Circuit().x(0, control=[1], control_state=[0]), + Circuit().x(0, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1316,7 +1545,7 @@ def foo( ), ), ( - Circuit().rx(0, 0.15, control=1, control_state=1), + Circuit().rx(0, 0.15, control=1, control_state=1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1332,7 +1561,7 @@ def foo( ), ), ( - Circuit().ry(0, 0.2, control=1, control_state=1), + Circuit().ry(0, 0.2, control=1, control_state=1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1348,7 +1577,7 @@ def foo( ), ), ( - Circuit().rz(0, 0.25, control=[1], control_state=[0]), + Circuit().rz(0, 0.25, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1364,7 +1593,7 @@ def foo( ), ), ( - Circuit().s(target=0, control=[1], control_state=[0]), + Circuit().s(target=0, control=[1], control_state=[0]).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1380,7 +1609,7 @@ def foo( ), ), ( - Circuit().t(target=1, control=[0], control_state=[0]), + Circuit().t(target=1, control=[0], control_state=[0]).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1396,7 +1625,7 @@ def foo( ), ), ( - Circuit().cphaseshift(target=0, control=1, angle=0.15), + Circuit().cphaseshift(target=0, control=1, angle=0.15).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1412,7 +1641,7 @@ def foo( ), ), ( - Circuit().ccnot(*[0, 1], target=2), + Circuit().ccnot(*[0, 1], target=2).measure(0).measure(1).measure(2), OpenQasmProgram( source="\n".join( [ @@ -1499,7 +1728,7 @@ def foo( ), ), ( - Circuit().bit_flip(0, 0.1), + Circuit().bit_flip(0, 0.1).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1514,7 +1743,7 @@ def foo( ), ), ( - Circuit().generalized_amplitude_damping(0, 0.1, 0.1), + Circuit().generalized_amplitude_damping(0, 0.1, 0.1).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1529,7 +1758,7 @@ def foo( ), ), ( - Circuit().phase_flip(0, 0.2), + Circuit().phase_flip(0, 0.2).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1544,7 +1773,7 @@ def foo( ), ), ( - Circuit().depolarizing(0, 0.5), + Circuit().depolarizing(0, 0.5).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1559,7 +1788,7 @@ def foo( ), ), ( - Circuit().amplitude_damping(0, 0.8), + Circuit().amplitude_damping(0, 0.8).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1574,7 +1803,7 @@ def foo( ), ), ( - Circuit().phase_damping(0, 0.1), + Circuit().phase_damping(0, 0.1).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1606,7 +1835,12 @@ def foo( Circuit() .rx(0, 0.15, control=2, control_state=0) .rx(1, 0.3, control=[2, 3]) - .cnot(target=0, control=[2, 3, 4]), + .cnot(target=0, control=[2, 3, 4]) + .measure(0) + .measure(1) + .measure(2) + .measure(3) + .measure(4), OpenQasmProgram( source="\n".join( [ @@ -1627,7 +1861,17 @@ def foo( ), ), ( - Circuit().cnot(0, 1).cnot(target=2, control=3).cnot(target=4, control=[5, 6]), + Circuit() + .cnot(0, 1) + .cnot(target=2, control=3) + .cnot(target=4, control=[5, 6]) + .measure(0) + .measure(1) + .measure(2) + .measure(3) + .measure(4) + .measure(5) + .measure(6), OpenQasmProgram( source="\n".join( [ @@ -1650,7 +1894,7 @@ def foo( ), ), ( - Circuit().h(0, power=-2.5).h(0, power=0), + Circuit().h(0, power=-2.5).h(0, power=0).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1666,7 +1910,7 @@ def foo( ), ), ( - Circuit().unitary(matrix=np.array([[0, 1], [1, 0]]), targets=[0]), + Circuit().unitary(matrix=np.array([[0, 1], [1, 0]]), targets=[0]).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1681,7 +1925,7 @@ def foo( ), ), ( - Circuit().pauli_channel(0, probX=0.1, probY=0.2, probZ=0.3), + Circuit().pauli_channel(0, probX=0.1, probY=0.2, probZ=0.3).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1696,7 +1940,7 @@ def foo( ), ), ( - Circuit().two_qubit_depolarizing(0, 1, probability=0.1), + Circuit().two_qubit_depolarizing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1712,7 +1956,7 @@ def foo( ), ), ( - Circuit().two_qubit_dephasing(0, 1, probability=0.1), + Circuit().two_qubit_dephasing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1728,7 +1972,7 @@ def foo( ), ), ( - Circuit().two_qubit_dephasing(0, 1, probability=0.1), + Circuit().two_qubit_dephasing(0, 1, probability=0.1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1787,13 +2031,15 @@ def foo( ), ), ( - Circuit().kraus( + Circuit() + .kraus( [0], matrices=[ np.array([[0.9486833j, 0], [0, 0.9486833j]]), np.array([[0, 0.31622777], [0.31622777, 0]]), ], - ), + ) + .measure(0), OpenQasmProgram( source="\n".join( [ @@ -1810,7 +2056,7 @@ def foo( ), ), ( - Circuit().rx(0, FreeParameter("theta")), + Circuit().rx(0, FreeParameter("theta")).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1826,7 +2072,7 @@ def foo( ), ), ( - Circuit().rx(0, np.pi), + Circuit().rx(0, np.pi).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1841,7 +2087,7 @@ def foo( ), ), ( - Circuit().rx(0, 2 * np.pi), + Circuit().rx(0, 2 * np.pi).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1856,7 +2102,7 @@ def foo( ), ), ( - Circuit().gphase(0.15).x(0), + Circuit().gphase(0.15).x(0).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1879,7 +2125,7 @@ def test_from_ir(expected_circuit, ir): def test_from_ir_inputs_updated(): - circuit = Circuit().rx(0, 0.2).ry(0, 0.1) + circuit = Circuit().rx(0, 0.2).ry(0, 0.1).measure(0) openqasm = OpenQasmProgram( source="\n".join( [ @@ -1902,7 +2148,7 @@ def test_from_ir_inputs_updated(): "expected_circuit, ir", [ ( - Circuit().h(0).cnot(0, 1), + Circuit().h(0).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1922,7 +2168,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().h(0).h(1), + Circuit().h(0).h(1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1942,7 +2188,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().h(0).h(1).cnot(0, 1), + Circuit().h(0).h(1).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1961,7 +2207,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().h(0).h(1).cnot(0, 1), + Circuit().h(0).h(1).cnot(0, 1).measure(0).measure(1), OpenQasmProgram( source="\n".join( [ @@ -1980,7 +2226,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().x(0), + Circuit().x(0).measure(0), OpenQasmProgram( source="\n".join( [ @@ -1998,7 +2244,7 @@ def test_from_ir_inputs_updated(): ), ), ( - Circuit().rx(0, FreeParameter("theta")).rx(0, 2 * FreeParameter("theta")), + Circuit().rx(0, FreeParameter("theta")).rx(0, 2 * FreeParameter("theta")).measure(0), OpenQasmProgram( source="\n".join( [ diff --git a/test/unit_tests/braket/circuits/test_measure.py b/test/unit_tests/braket/circuits/test_measure.py new file mode 100644 index 000000000..8911da5e4 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_measure.py @@ -0,0 +1,100 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +import pytest + +from braket.circuits.measure import Measure +from braket.circuits.quantum_operator import QuantumOperator +from braket.circuits.serialization import ( + IRType, + OpenQASMSerializationProperties, + QubitReferenceType, +) + + +@pytest.fixture +def measure(): + return Measure() + + +def test_is_operator(measure): + assert isinstance(measure, QuantumOperator) + + +def test_equality(): + measure1 = Measure() + measure2 = Measure() + non_measure = "non measure" + + assert measure1 == measure2 + assert measure1 is not measure2 + assert measure1 != non_measure + + +def test_ascii_symbols(measure): + assert measure.ascii_symbols == ("M",) + + +def test_str(measure): + assert str(measure) == measure.name + + +@pytest.mark.parametrize( + "ir_type, serialization_properties, expected_exception, expected_message", + [ + ( + IRType.JAQCD, + None, + NotImplementedError, + "measure instructions are not supported with JAQCD.", + ), + ("invalid-ir-type", None, ValueError, "supplied ir_type invalid-ir-type is not supported."), + ], +) +def test_measure_to_ir( + ir_type, serialization_properties, expected_exception, expected_message, measure +): + with pytest.raises(expected_exception) as exc: + measure.to_ir(ir_type=ir_type, serialization_properties=serialization_properties) + assert exc.value.args[0] == expected_message + + +@pytest.mark.parametrize( + "measure, target, serialization_properties, expected_ir", + [ + ( + Measure(), + [0], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "b[0] = measure q[0];", + ), + ( + Measure(), + [1, 4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "\n".join( + [ + "b[0] = measure $1;", + "b[1] = measure $4;", + ] + ), + ), + ], +) +def test_measure_to_ir_openqasm(measure, target, serialization_properties, expected_ir): + assert ( + measure.to_ir( + target, ir_type=IRType.OPENQASM, serialization_properties=serialization_properties + ) + == expected_ir + ) diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py index 96df634cb..f78b4e746 100644 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -1019,3 +1019,81 @@ def __init__(self): "T : │ 0 │", ) _assert_correct_diagram(circ, expected) + + +def test_measure(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 2, 3]) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───●───────────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ", + "q1 : ───────┤ X ├───●───────────────", + " └───┘ │ ", + " ┌─┴─┐ ┌───┐ ", + "q2 : ─────────────┤ X ├───●───┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q3 : ───────────────────┤ X ├─┤ M ├─", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + + +def test_measure_with_multiple_measures(): + circ = Circuit().h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3).measure([0, 2]).measure(3).measure(1) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───●───────────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q1 : ───────┤ X ├───●─────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q2 : ─────────────┤ X ├───●───┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q3 : ───────────────────┤ X ├─┤ M ├─", + " └───┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │", + ) + _assert_correct_diagram(circ, expected) + _assert_correct_diagram(circ, expected) + + +def test_measure_multiple_instructions_after(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .cnot(1, 2) + .cnot(2, 3) + .measure(0) + .measure(1) + .h(3) + .cnot(3, 4) + .measure([2, 3]) + ) + expected = ( + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───●───────────────┤ M ├─────────────", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q1 : ───────┤ X ├───●─────────┤ M ├─────────────", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ", + "q2 : ─────────────┤ X ├───●───────────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌───┐ ┌───┐ ", + "q3 : ───────────────────┤ X ├─┤ H ├───●───┤ M ├─", + " └───┘ └───┘ │ └───┘ ", + " ┌─┴─┐ ", + "q4 : ───────────────────────────────┤ X ├───────", + " └───┘ ", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py index 8c0f8d9a9..43dd06db7 100644 --- a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py @@ -262,16 +262,6 @@ def malformatted_results_1(task_metadata_shots, additional_metadata): ).json() -@pytest.fixture -def malformatted_results_2(task_metadata_shots, additional_metadata): - return GateModelTaskResult( - measurementProbabilities={"011000": 0.9999999999999982}, - measuredQubits=[0], - taskMetadata=task_metadata_shots, - additionalMetadata=additional_metadata, - ).json() - - @pytest.fixture def openqasm_result_obj_shots(task_metadata_shots, additional_metadata_openqasm): return GateModelTaskResult.construct( @@ -484,11 +474,6 @@ def test_shots_no_measurements_no_measurement_probs(malformatted_results_1): GateModelQuantumTaskResult.from_string(malformatted_results_1) -@pytest.mark.xfail(raises=ValueError) -def test_measurements_measured_qubits_mismatch(malformatted_results_2): - GateModelQuantumTaskResult.from_string(malformatted_results_2) - - @pytest.mark.parametrize("ir_result,expected_result", test_ir_results) def test_calculate_ir_results(ir_result, expected_result): ir_string = jaqcd.Program( From 600baf24fdea9307db0aaa2c14be6363b8ee73f7 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 1 Apr 2024 17:49:47 +0000 Subject: [PATCH 47/98] prepare release v1.76.0 --- CHANGELOG.md | 14 ++++++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a9aa145..fa01b5469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## v1.76.0 (2024-04-01) + +### Features + + * add support for OpenQASM measure on a subset of qubits + +### Bug Fixes and Other Changes + + * restore the dependent test back to pennylane + +### Documentation Changes + + * fix GPI2 gate matrix representation + ## v1.75.0 (2024-03-28) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 9c4e62904..8fd998330 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.75.1.dev0" +__version__ = "1.76.0" From d5580dc80b59b9c07ca639e1a20ce37d4ffb2ed4 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 1 Apr 2024 17:49:47 +0000 Subject: [PATCH 48/98] update development version to v1.76.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8fd998330..2721571fe 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.0" +__version__ = "1.76.1.dev0" From c57a9ba15f0ffb412c9717394690633131538088 Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Fri, 5 Apr 2024 09:30:11 -0700 Subject: [PATCH 49/98] fix: prevent repeated measurements on a qubit (#937) * fix: prevent repeated measurements on a qubit * break repeated qubits into two error messages for repeat in measured_targets or repeat in target_qubits --- src/braket/circuits/circuit.py | 12 +++++++++++- test/unit_tests/braket/circuits/test_circuit.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 61fcd38bb..a4b48629d 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -13,6 +13,7 @@ from __future__ import annotations +from collections import Counter from collections.abc import Callable, Iterable from numbers import Number from typing import Any, Optional, TypeVar, Union @@ -762,7 +763,7 @@ def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: if target_qubits: # Check if the target_qubits are already measured - if self._measure_targets and all( + if self._measure_targets and any( target in self._measure_targets for target in target_qubits ): intersection = set(target_qubits) & set(self._measure_targets) @@ -770,6 +771,15 @@ def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " "more than once." ) + # Check if there are repeated qubits in the same measurement + if len(target_qubits) != len(set(target_qubits)): + intersection = [ + qubit for qubit, count in Counter(target_qubits).items() if count > 1 + ] + raise ValueError( + f"cannot repeat qubit(s) {', '.join(map(str, intersection))} " + "in the same measurement." + ) self._add_measure(target_qubits=target_qubits) else: # Check if any qubits are already measured diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 9b34d6629..7680f9b4f 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -718,6 +718,18 @@ def test_measure_same_qubit_twice(): Circuit().h(0).cnot(0, 1).measure(0).measure(1).measure(0) +def test_measure_same_qubit_twice_with_list(): + message = "cannot measure the same qubit\\(s\\) 0 more than once." + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).measure(0).measure([0, 1]) + + +def test_measure_same_qubit_twice_with_one_measure(): + message = "cannot repeat qubit\\(s\\) 0 in the same measurement." + with pytest.raises(ValueError, match=message): + Circuit().h(0).cnot(0, 1).measure([0, 0, 0]) + + def test_measure_empty_measure_after_measure_with_targets(): message = "cannot measure the same qubit\\(s\\) 0, 1 more than once." with pytest.raises(ValueError, match=message): From b98bce577a2105f990a4e1aaf726b3802aa45f48 Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:36:44 -0700 Subject: [PATCH 50/98] fix: Support single-register measurements in `from_ir` (#934) * fix: add support for measuring a single register in an OpenQASM program * add round trip transformation test from circuit to OQ3 to circuit --- src/braket/circuits/braket_program_context.py | 5 ++- .../braket/circuits/test_circuit.py | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index a17864ed4..852dfba80 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -167,5 +167,6 @@ def add_measure(self, target: tuple[int]) -> None: Args: target (tuple[int]): the target qubits to be measured. """ - instruction = Instruction(Measure(), list(target)) - self._circuit.add_instruction(instruction) + for index, qubit in enumerate(target): + instruction = Instruction(Measure(index=index), qubit) + self._circuit.add_instruction(instruction) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 7680f9b4f..51752e3d4 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -811,6 +811,48 @@ def test_from_ir_with_measure(): assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == expected_circ +def test_from_ir_with_single_measure(): + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "h q[0];", + "cnot q[0], q[1];", + "b = measure q;", + ] + ), + inputs={}, + ) + expected_circ = Circuit().h(0).cnot(0, 1).measure(0).measure(1) + assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == expected_circ + + +def test_from_ir_round_trip_transformation(): + circuit = Circuit().h(0).cnot(0, 1).measure(0).measure(1) + ir = OpenQasmProgram( + source="\n".join( + [ + "OPENQASM 3.0;", + "bit[2] b;", + "qubit[2] q;", + "h q[0];", + "cnot q[0], q[1];", + "b[0] = measure q[0];", + "b[1] = measure q[1];", + ] + ), + inputs={}, + ) + new_ir = circuit.to_ir("OPENQASM") + new_circuit = Circuit.from_ir(new_ir) + + assert new_ir == ir + assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == circuit + assert new_circuit == circuit + + def test_add_with_instruction_with_default(cnot_instr): circ = Circuit().add(cnot_instr) assert circ == Circuit().add_instruction(cnot_instr) From 1beeaac7d1c524e7371aaf572a70a00aaeb077fe Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Apr 2024 16:17:27 +0000 Subject: [PATCH 51/98] prepare release v1.76.1 --- CHANGELOG.md | 7 +++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa01b5469..e125c940c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v1.76.1 (2024-04-08) + +### Bug Fixes and Other Changes + + * Support single-register measurements in `from_ir` + * prevent repeated measurements on a qubit + ## v1.76.0 (2024-04-01) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 2721571fe..7a807475d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.1.dev0" +__version__ = "1.76.1" From e93b8a7c26d1fb9b0b8dc855895ee9b963ec7897 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Apr 2024 16:17:27 +0000 Subject: [PATCH 52/98] update development version to v1.76.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7a807475d..5919ee261 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.1" +__version__ = "1.76.2.dev0" From 1de7dbe81a7388e4ed430c5d9876818373563a39 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:52:17 -0700 Subject: [PATCH 53/98] fix: backwards compatiblity for local detuning (#942) --- .../braket/ahs/test_analog_hamiltonian_simulation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py index bdc3e92f2..aeb969869 100644 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -177,7 +177,12 @@ def test_discretize(register, driving_field, shifting_field): "values": ["-125664000.0", "-125664000.0", "125664000.0", "125664000.0"], }, } - assert discretized_json["hamiltonian"]["shiftingFields"][0]["magnitude"] == { + local_detuning = ( + discretized_json["hamiltonian"]["shiftingFields"][0]["magnitude"] + if "shiftingFields" in discretized_json["hamiltonian"].keys() + else discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] + ) + assert local_detuning == { "pattern": ["0.50", "1.00", "0.50", "0.50", "0.50", "0.50"], "time_series": { "times": ["0E-9", "0.000003000"], From e17918bfbb9217c2d5d2f6687cfd8beeba2c431e Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Apr 2024 21:42:01 +0000 Subject: [PATCH 54/98] prepare release v1.76.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e125c940c..d1aa55863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.76.2 (2024-04-08) + +### Bug Fixes and Other Changes + + * backwards compatiblity for local detuning + ## v1.76.1 (2024-04-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5919ee261..15c11b7c4 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.2.dev0" +__version__ = "1.76.2" From 5b21c12121341ba3c42b94ef15251e0bee192498 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 8 Apr 2024 21:42:01 +0000 Subject: [PATCH 55/98] update development version to v1.76.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 15c11b7c4..8692940be 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.2" +__version__ = "1.76.3.dev0" From 64a25363580ed159fe716955fda4aaebdbc06609 Mon Sep 17 00:00:00 2001 From: Li Li <60371004+tachikoma-li@users.noreply.github.com> Date: Wed, 10 Apr 2024 02:09:24 +1000 Subject: [PATCH 56/98] fix: Replace pkg_resources with importlib.metadata (#935) --- doc/conf.py | 5 ++--- setup.py | 2 +- src/braket/devices/local_simulator.py | 12 +++++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 2a8193e55..a2548fc65 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,12 +1,11 @@ """Sphinx configuration.""" import datetime - -import pkg_resources +from importlib.metadata import version # Sphinx configuration below. project = "amazon-braket-sdk" -version = pkg_resources.require(project)[0].version +version = version(project) release = version copyright = "{}, Amazon.com".format(datetime.datetime.now().year) diff --git a/setup.py b/setup.py index 6c0a9e984..34d220161 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,6 @@ "amazon-braket-schemas>=1.21.0", "amazon-braket-default-simulator>=1.21.2", "oqpy~=0.3.5", - "setuptools", "backoff", "boltons", "boto3>=1.28.53", @@ -41,6 +40,7 @@ "openpulse", "openqasm3", "sympy", + "backports.entry-points-selectable", ], extras_require={ "test": [ diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index faee13fdf..69fcfdaff 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -13,14 +13,13 @@ from __future__ import annotations +import sys from functools import singledispatchmethod from itertools import repeat from multiprocessing import Pool from os import cpu_count from typing import Any, Optional, Union -import pkg_resources - from braket.ahs.analog_hamiltonian_simulation import AnalogHamiltonianSimulation from braket.annealing.problem import Problem from braket.circuits import Circuit @@ -39,9 +38,12 @@ from braket.tasks.local_quantum_task import LocalQuantumTask from braket.tasks.local_quantum_task_batch import LocalQuantumTaskBatch -_simulator_devices = { - entry.name: entry for entry in pkg_resources.iter_entry_points("braket.simulators") -} +if sys.version_info.minor == 9: + from backports.entry_points_selectable import entry_points +else: + from importlib.metadata import entry_points + +_simulator_devices = {entry.name: entry for entry in entry_points(group="braket.simulators")} class LocalSimulator(Device): From c68722302aec90d0caa2242dec9e0997afb8246d Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:43:03 -0400 Subject: [PATCH 57/98] doc: Improve gphase unitary matrix definition in docstring (#944) * Improve gphase unitary matrix definition in docstring * Remove extra whitespace --------- Co-authored-by: Abe Coull <85974725+math411@users.noreply.github.com> --- src/braket/circuits/gates.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index b93b2dfa7..3bfb16731 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -220,7 +220,9 @@ class GPhase(AngledGate): Unitary matrix: - .. math:: \mathtt{gphase}(\gamma) = e^(i \gamma) I_1. + .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I = \begin{bmatrix} + e^{i \gamma} & 0 \\ + 0 & e^{i \gamma} \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -277,7 +279,9 @@ def gphase( Unitary matrix: - .. math:: \mathtt{gphase}(\gamma) = e^(i \gamma) I_1. + .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I = \begin{bmatrix} + e^{i \gamma} & 0 \\ + 0 & e^{i \gamma} \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): Phase in radians. From bc8e56a79a500ef1bf046999e90a3f204a0d6d64 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 9 Apr 2024 17:52:29 +0000 Subject: [PATCH 58/98] prepare release v1.76.3 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1aa55863..c97490e5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.76.3 (2024-04-09) + +### Bug Fixes and Other Changes + + * Replace pkg_resources with importlib.metadata + +### Documentation Changes + + * Improve gphase unitary matrix definition in docstring + ## v1.76.2 (2024-04-08) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8692940be..51f057a07 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.3.dev0" +__version__ = "1.76.3" From 3ea6899074c68f788ee2d4271e415ba592dd793c Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 9 Apr 2024 17:52:29 +0000 Subject: [PATCH 59/98] update development version to v1.76.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 51f057a07..e9af02fbc 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.3" +__version__ = "1.76.4.dev0" From e2da4ff2a77c1462636b40442668d5440b2cd126 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:35:11 -0700 Subject: [PATCH 60/98] feat: rename shifting field to local detuning (#943) --- src/braket/ahs/local_detuning.py | 161 +++++++++++++++++++++++++++++++ src/braket/ahs/shifting_field.py | 153 ++--------------------------- 2 files changed, 167 insertions(+), 147 deletions(-) create mode 100644 src/braket/ahs/local_detuning.py diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py new file mode 100644 index 000000000..574b985d3 --- /dev/null +++ b/src/braket/ahs/local_detuning.py @@ -0,0 +1,161 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from __future__ import annotations + +from braket.ahs.discretization_types import DiscretizationProperties +from braket.ahs.field import Field +from braket.ahs.hamiltonian import Hamiltonian +from braket.ahs.pattern import Pattern +from braket.timings.time_series import StitchBoundaryCondition, TimeSeries + + +class LocalDetuning(Hamiltonian): + def __init__(self, magnitude: Field) -> None: + r"""Creates a Hamiltonian term :math:`H_{shift}` representing the local detuning + that changes the energy of the Rydberg level in an AnalogHamiltonianSimulation, + defined by the formula + + .. math:: + H_{shift} (t) := -\Delta(t) \sum_k h_k | r_k \rangle \langle r_k | + + where + + :math:`\Delta(t)` is the magnitude of the frequency shift in rad/s, + + :math:`h_k` is the site coefficient of atom :math:`k`, + a dimensionless real number between 0 and 1, + + :math:`|r_k \rangle` is the Rydberg state of atom :math:`k`. + + with the sum :math:`\sum_k` taken over all target atoms. + + Args: + magnitude (Field): containing the global magnitude time series :math:`\Delta(t)`, + where time is measured in seconds (s) and values are measured in rad/s, and the + local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. + """ + super().__init__() + self._magnitude = magnitude + + @property + def terms(self) -> list[Hamiltonian]: + return [self] + + @property + def magnitude(self) -> Field: + r"""Field: containing the global magnitude time series :math:`\Delta(t)`, + where time is measured in seconds (s) and values measured in rad/s) + and the local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. + """ + return self._magnitude + + @staticmethod + def from_lists(times: list[float], values: list[float], pattern: list[float]) -> LocalDetuning: + """Get the shifting field from a set of time points, values and pattern + + Args: + times (list[float]): The time points of the shifting field + values (list[float]): The values of the shifting field + pattern (list[float]): The pattern of the shifting field + + Raises: + ValueError: If the length of times and values differs. + + Returns: + LocalDetuning: The shifting field obtained + """ + if len(times) != len(values): + raise ValueError("The length of the times and values lists must be equal.") + + magnitude = TimeSeries() + for t, v in zip(times, values): + magnitude.put(t, v) + shift = LocalDetuning(Field(magnitude, Pattern(pattern))) + + return shift + + def stitch( + self, other: LocalDetuning, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN + ) -> LocalDetuning: + """Stitches two shifting fields based on TimeSeries.stitch method. + The time points of the second LocalDetuning are shifted such that the first time point of + the second LocalDetuning coincides with the last time point of the first LocalDetuning. + The boundary point value is handled according to StitchBoundaryCondition argument value. + + Args: + other (LocalDetuning): The second local detuning to be stitched with. + boundary (StitchBoundaryCondition): {"mean", "left", "right"}. Boundary point handler. + + Possible options are + - "mean" - take the average of the boundary value points of the first + and the second time series. + - "left" - use the last value from the left time series as the boundary point. + - "right" - use the first value from the right time series as the boundary point. + + Raises: + ValueError: The LocalDetuning patterns differ. + + Returns: + LocalDetuning: The stitched LocalDetuning object. + + Example (StitchBoundaryCondition.MEAN): + :: + time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) + time_series_2 = TimeSeries.from_lists(times=[0.2, 0.4], values=[4, 5]) + + stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.MEAN) + + Result: + stitch_ts.times() = [0, 0.1, 0.3] + stitch_ts.values() = [1, 3, 5] + + Example (StitchBoundaryCondition.LEFT): + :: + stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.LEFT) + + Result: + stitch_ts.times() = [0, 0.1, 0.3] + stitch_ts.values() = [1, 2, 5] + + Example (StitchBoundaryCondition.RIGHT): + :: + stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.RIGHT) + + Result: + stitch_ts.times() = [0, 0.1, 0.3] + stitch_ts.values() = [1, 4, 5] + """ + if not (self.magnitude.pattern.series == other.magnitude.pattern.series): + raise ValueError("The LocalDetuning pattern for both fields must be equal.") + + new_ts = self.magnitude.time_series.stitch(other.magnitude.time_series, boundary) + return LocalDetuning(Field(new_ts, self.magnitude.pattern)) + + def discretize(self, properties: DiscretizationProperties) -> LocalDetuning: + """Creates a discretized version of the LocalDetuning. + + Args: + properties (DiscretizationProperties): Capabilities of a device that represent the + resolution with which the device can implement the parameters. + + Returns: + LocalDetuning: A new discretized LocalDetuning. + """ + shifting_parameters = properties.rydberg.rydbergLocal + discretized_magnitude = self.magnitude.discretize( + time_resolution=shifting_parameters.timeResolution, + value_resolution=shifting_parameters.commonDetuningResolution, + pattern_resolution=shifting_parameters.localDetuningResolution, + ) + return LocalDetuning(discretized_magnitude) diff --git a/src/braket/ahs/shifting_field.py b/src/braket/ahs/shifting_field.py index 7bed1fe8a..34e24191e 100644 --- a/src/braket/ahs/shifting_field.py +++ b/src/braket/ahs/shifting_field.py @@ -11,151 +11,10 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from __future__ import annotations +from braket.ahs.local_detuning import LocalDetuning -from braket.ahs.discretization_types import DiscretizationProperties -from braket.ahs.field import Field -from braket.ahs.hamiltonian import Hamiltonian -from braket.ahs.pattern import Pattern -from braket.timings.time_series import StitchBoundaryCondition, TimeSeries - - -class ShiftingField(Hamiltonian): - def __init__(self, magnitude: Field) -> None: - r"""Creates a Hamiltonian term :math:`H_{shift}` representing the shifting field - that changes the energy of the Rydberg level in an AnalogHamiltonianSimulation, - defined by the formula - - .. math:: - H_{shift} (t) := -\Delta(t) \sum_k h_k | r_k \rangle \langle r_k | - - where - - :math:`\Delta(t)` is the magnitude of the frequency shift in rad/s, - - :math:`h_k` is the site coefficient of atom :math:`k`, - a dimensionless real number between 0 and 1, - - :math:`|r_k \rangle` is the Rydberg state of atom :math:`k`. - - with the sum :math:`\sum_k` taken over all target atoms. - - Args: - magnitude (Field): containing the global magnitude time series :math:`\Delta(t)`, - where time is measured in seconds (s) and values are measured in rad/s, and the - local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. - """ - super().__init__() - self._magnitude = magnitude - - @property - def terms(self) -> list[Hamiltonian]: - return [self] - - @property - def magnitude(self) -> Field: - r"""Field: containing the global magnitude time series :math:`\Delta(t)`, - where time is measured in seconds (s) and values measured in rad/s) - and the local pattern :math:`h_k` of dimensionless real numbers between 0 and 1. - """ - return self._magnitude - - @staticmethod - def from_lists(times: list[float], values: list[float], pattern: list[float]) -> ShiftingField: - """Get the shifting field from a set of time points, values and pattern - - Args: - times (list[float]): The time points of the shifting field - values (list[float]): The values of the shifting field - pattern (list[float]): The pattern of the shifting field - - Raises: - ValueError: If the length of times and values differs. - - Returns: - ShiftingField: The shifting field obtained - """ - if len(times) != len(values): - raise ValueError("The length of the times and values lists must be equal.") - - magnitude = TimeSeries() - for t, v in zip(times, values): - magnitude.put(t, v) - shift = ShiftingField(Field(magnitude, Pattern(pattern))) - - return shift - - def stitch( - self, other: ShiftingField, boundary: StitchBoundaryCondition = StitchBoundaryCondition.MEAN - ) -> ShiftingField: - """Stitches two shifting fields based on TimeSeries.stitch method. - The time points of the second ShiftingField are shifted such that the first time point of - the second ShiftingField coincides with the last time point of the first ShiftingField. - The boundary point value is handled according to StitchBoundaryCondition argument value. - - Args: - other (ShiftingField): The second shifting field to be stitched with. - boundary (StitchBoundaryCondition): {"mean", "left", "right"}. Boundary point handler. - - Possible options are - - "mean" - take the average of the boundary value points of the first - and the second time series. - - "left" - use the last value from the left time series as the boundary point. - - "right" - use the first value from the right time series as the boundary point. - - Raises: - ValueError: The ShiftingField patterns differ. - - Returns: - ShiftingField: The stitched ShiftingField object. - - Example (StitchBoundaryCondition.MEAN): - :: - time_series_1 = TimeSeries.from_lists(times=[0, 0.1], values=[1, 2]) - time_series_2 = TimeSeries.from_lists(times=[0.2, 0.4], values=[4, 5]) - - stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.MEAN) - - Result: - stitch_ts.times() = [0, 0.1, 0.3] - stitch_ts.values() = [1, 3, 5] - - Example (StitchBoundaryCondition.LEFT): - :: - stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.LEFT) - - Result: - stitch_ts.times() = [0, 0.1, 0.3] - stitch_ts.values() = [1, 2, 5] - - Example (StitchBoundaryCondition.RIGHT): - :: - stitch_ts = time_series_1.stitch(time_series_2, boundary=StitchBoundaryCondition.RIGHT) - - Result: - stitch_ts.times() = [0, 0.1, 0.3] - stitch_ts.values() = [1, 4, 5] - """ - if not (self.magnitude.pattern.series == other.magnitude.pattern.series): - raise ValueError("The ShiftingField pattern for both fields must be equal.") - - new_ts = self.magnitude.time_series.stitch(other.magnitude.time_series, boundary) - return ShiftingField(Field(new_ts, self.magnitude.pattern)) - - def discretize(self, properties: DiscretizationProperties) -> ShiftingField: - """Creates a discretized version of the ShiftingField. - - Args: - properties (DiscretizationProperties): Capabilities of a device that represent the - resolution with which the device can implement the parameters. - - Returns: - ShiftingField: A new discretized ShiftingField. - """ - shifting_parameters = properties.rydberg.rydbergLocal - discretized_magnitude = self.magnitude.discretize( - time_resolution=shifting_parameters.timeResolution, - value_resolution=shifting_parameters.commonDetuningResolution, - pattern_resolution=shifting_parameters.localDetuningResolution, - ) - return ShiftingField(discretized_magnitude) +# The class `ShiftingField` is deprecated. Please use `LocalDetuning` instead. +# This file and class will be removed in a future version. +# We are retaining this now to avoid breaking backwards compatibility for users already +# utilizing this nomenclature. +ShiftingField = LocalDetuning From bf075075a2aed6aeadaf65c5743707c41cda29b4 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 01:12:49 +0000 Subject: [PATCH 61/98] prepare release v1.77.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c97490e5a..3f8fb8909 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.0 (2024-04-10) + +### Features + + * rename shifting field to local detuning + ## v1.76.3 (2024-04-09) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index e9af02fbc..158bc4c48 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.76.4.dev0" +__version__ = "1.77.0" From f5565675dd3e6cdfc8cbac1ce1420107b7108850 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 01:12:49 +0000 Subject: [PATCH 62/98] update development version to v1.77.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 158bc4c48..f2ce90f8f 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.0" +__version__ = "1.77.1.dev0" From 78a49696f618b63a997929c3407be29f67535de7 Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Wed, 10 Apr 2024 13:50:34 -0700 Subject: [PATCH 63/98] fix: add measure qubit targets in braket_program_context (#947) --- src/braket/circuits/braket_program_context.py | 4 ++++ test/unit_tests/braket/circuits/test_circuit.py | 10 +++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/braket/circuits/braket_program_context.py b/src/braket/circuits/braket_program_context.py index 852dfba80..4371637d3 100644 --- a/src/braket/circuits/braket_program_context.py +++ b/src/braket/circuits/braket_program_context.py @@ -170,3 +170,7 @@ def add_measure(self, target: tuple[int]) -> None: for index, qubit in enumerate(target): instruction = Instruction(Measure(index=index), qubit) self._circuit.add_instruction(instruction) + if self._circuit._measure_targets: + self._circuit._measure_targets.append(qubit) + else: + self._circuit._measure_targets = [qubit] diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index 51752e3d4..d7f6568d4 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -839,18 +839,14 @@ def test_from_ir_round_trip_transformation(): "qubit[2] q;", "h q[0];", "cnot q[0], q[1];", - "b[0] = measure q[0];", - "b[1] = measure q[1];", + "b = measure q;", ] ), inputs={}, ) - new_ir = circuit.to_ir("OPENQASM") - new_circuit = Circuit.from_ir(new_ir) - assert new_ir == ir - assert Circuit.from_ir(source=ir.source, inputs=ir.inputs) == circuit - assert new_circuit == circuit + assert Circuit.from_ir(ir) == Circuit.from_ir(circuit.to_ir("OPENQASM")) + assert circuit.to_ir("OPENQASM") == Circuit.from_ir(ir).to_ir("OPENQASM") def test_add_with_instruction_with_default(cnot_instr): From fc045fa096313855397ffdeb37566a6e1c4098f6 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 21:13:43 +0000 Subject: [PATCH 64/98] prepare release v1.77.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8fb8909..9384714f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.1 (2024-04-10) + +### Bug Fixes and Other Changes + + * add measure qubit targets in braket_program_context + ## v1.77.0 (2024-04-10) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index f2ce90f8f..64b837643 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.1.dev0" +__version__ = "1.77.1" From a60d715f0643bcc4ae966c4ba68c012c77030cbc Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 21:13:43 +0000 Subject: [PATCH 65/98] update development version to v1.77.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 64b837643..7d0928a71 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.1" +__version__ = "1.77.2.dev0" From e1564a42ac8b898f021bc7d2fbae370338f5ea50 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:34:56 -0700 Subject: [PATCH 66/98] fix: remove shifting field from testing (#948) --- setup.py | 2 +- src/braket/ahs/__init__.py | 1 + .../ahs/analog_hamiltonian_simulation.py | 10 ++-- .../ahs/test_analog_hamiltonian_simulation.py | 20 ++++---- ...ifting_field.py => test_local_detuning.py} | 48 +++++++++---------- ...alog_hamiltonian_simulation_task_result.py | 2 +- 6 files changed, 40 insertions(+), 43 deletions(-) rename test/unit_tests/braket/ahs/{test_shifting_field.py => test_local_detuning.py} (73%) diff --git a/setup.py b/setup.py index 34d220161..821748ef3 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ packages=find_namespace_packages(where="src", exclude=("test",)), package_dir={"": "src"}, install_requires=[ - "amazon-braket-schemas>=1.21.0", + "amazon-braket-schemas>=1.21.3", "amazon-braket-default-simulator>=1.21.2", "oqpy~=0.3.5", "backoff", diff --git a/src/braket/ahs/__init__.py b/src/braket/ahs/__init__.py index 8a9fd2666..5bf3cc61e 100644 --- a/src/braket/ahs/__init__.py +++ b/src/braket/ahs/__init__.py @@ -17,5 +17,6 @@ from braket.ahs.driving_field import DrivingField # noqa: F401 from braket.ahs.field import Field # noqa: F401 from braket.ahs.hamiltonian import Hamiltonian # noqa: F401 +from braket.ahs.local_detuning import LocalDetuning # noqa: F401 from braket.ahs.pattern import Pattern # noqa: F401 from braket.ahs.shifting_field import ShiftingField # noqa: F401 diff --git a/src/braket/ahs/analog_hamiltonian_simulation.py b/src/braket/ahs/analog_hamiltonian_simulation.py index 8d8cce10f..af02471e2 100644 --- a/src/braket/ahs/analog_hamiltonian_simulation.py +++ b/src/braket/ahs/analog_hamiltonian_simulation.py @@ -21,12 +21,12 @@ from braket.ahs.discretization_types import DiscretizationError, DiscretizationProperties from braket.ahs.driving_field import DrivingField from braket.ahs.hamiltonian import Hamiltonian -from braket.ahs.shifting_field import ShiftingField +from braket.ahs.local_detuning import LocalDetuning from braket.device_schema import DeviceActionType class AnalogHamiltonianSimulation: - SHIFTING_FIELDS_PROPERTY = "shifting_fields" + LOCAL_DETUNING_PROPERTY = "local_detuning" DRIVING_FIELDS_PROPERTY = "driving_fields" def __init__(self, register: AtomArrangement, hamiltonian: Hamiltonian) -> None: @@ -74,7 +74,7 @@ def _hamiltonian_to_ir(self) -> ir.Hamiltonian: terms[term_type].append(term_ir) return ir.Hamiltonian( drivingFields=terms[AnalogHamiltonianSimulation.DRIVING_FIELDS_PROPERTY], - shiftingFields=terms[AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY], + localDetuning=terms[AnalogHamiltonianSimulation.LOCAL_DETUNING_PROPERTY], ) def discretize(self, device: AwsDevice) -> AnalogHamiltonianSimulation: # noqa @@ -116,8 +116,8 @@ def _get_term_ir( @_get_term_ir.register -def _(term: ShiftingField) -> tuple[str, ir.ShiftingField]: - return AnalogHamiltonianSimulation.SHIFTING_FIELDS_PROPERTY, ir.ShiftingField( +def _(term: LocalDetuning) -> tuple[str, ir.LocalDetuning]: + return AnalogHamiltonianSimulation.LOCAL_DETUNING_PROPERTY, ir.LocalDetuning( magnitude=ir.PhysicalField( time_series=ir.TimeSeries( times=term.magnitude.time_series.times(), diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py index aeb969869..65cc2a074 100644 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -23,7 +23,7 @@ AtomArrangement, DiscretizationError, DrivingField, - ShiftingField, + LocalDetuning, SiteType, ) from braket.ahs.atom_arrangement import AtomArrangementItem @@ -61,8 +61,8 @@ def driving_field(): @pytest.fixture -def shifting_field(): - return ShiftingField( +def local_detuning(): + return LocalDetuning( Field( TimeSeries().put(0.0, -1.25664e8).put(3.0e-6, 1.25664e8), Pattern([0.5, 1.0, 0.5, 0.5, 0.5, 0.5]), @@ -78,8 +78,8 @@ def test_create(): assert mock1 == ahs.hamiltonian -def test_to_ir(register, driving_field, shifting_field): - hamiltonian = driving_field + shifting_field +def test_to_ir(register, driving_field, local_detuning): + hamiltonian = driving_field + local_detuning ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) problem = ahs.to_ir() assert Program.parse_raw(problem.json()) == problem @@ -123,8 +123,8 @@ def test_invalid_action_name(): AnalogHamiltonianSimulation(register=Mock(), hamiltonian=Mock()).discretize(device) -def test_discretize(register, driving_field, shifting_field): - hamiltonian = driving_field + shifting_field +def test_discretize(register, driving_field, local_detuning): + hamiltonian = driving_field + local_detuning ahs = AnalogHamiltonianSimulation(register=register, hamiltonian=hamiltonian) action = Mock() @@ -177,11 +177,7 @@ def test_discretize(register, driving_field, shifting_field): "values": ["-125664000.0", "-125664000.0", "125664000.0", "125664000.0"], }, } - local_detuning = ( - discretized_json["hamiltonian"]["shiftingFields"][0]["magnitude"] - if "shiftingFields" in discretized_json["hamiltonian"].keys() - else discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] - ) + local_detuning = discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] assert local_detuning == { "pattern": ["0.50", "1.00", "0.50", "0.50", "0.50", "0.50"], "time_series": { diff --git a/test/unit_tests/braket/ahs/test_shifting_field.py b/test/unit_tests/braket/ahs/test_local_detuning.py similarity index 73% rename from test/unit_tests/braket/ahs/test_shifting_field.py rename to test/unit_tests/braket/ahs/test_local_detuning.py index 0249b8758..4c4f94675 100644 --- a/test/unit_tests/braket/ahs/test_shifting_field.py +++ b/test/unit_tests/braket/ahs/test_local_detuning.py @@ -16,49 +16,49 @@ import pytest from braket.ahs.hamiltonian import Hamiltonian -from braket.ahs.shifting_field import ShiftingField +from braket.ahs.local_detuning import LocalDetuning from braket.timings.time_series import StitchBoundaryCondition @pytest.fixture -def default_shifting_field(): - return ShiftingField(Mock()) +def default_local_detuning(): + return LocalDetuning(Mock()) def test_create(): mock0 = Mock() - field = ShiftingField(magnitude=mock0) + field = LocalDetuning(magnitude=mock0) assert mock0 == field.magnitude -def test_add_hamiltonian(default_shifting_field): - expected = [default_shifting_field, Mock(), Mock(), Mock()] +def test_add_hamiltonian(default_local_detuning): + expected = [default_local_detuning, Mock(), Mock(), Mock()] result = expected[0] + Hamiltonian([expected[1], expected[2], expected[3]]) assert result.terms == expected -def test_add_to_hamiltonian(default_shifting_field): - expected = [Mock(), Mock(), Mock(), default_shifting_field] +def test_add_to_hamiltonian(default_local_detuning): + expected = [Mock(), Mock(), Mock(), default_local_detuning] result = Hamiltonian([expected[0], expected[1], expected[2]]) + expected[3] assert result.terms == expected def test_add_to_other(): - field0 = ShiftingField(Mock()) - field1 = ShiftingField(Mock()) + field0 = LocalDetuning(Mock()) + field1 = LocalDetuning(Mock()) result = field0 + field1 assert type(result) is Hamiltonian assert result.terms == [field0, field1] -def test_add_to_self(default_shifting_field): - result = default_shifting_field + default_shifting_field +def test_add_to_self(default_local_detuning): + result = default_local_detuning + default_local_detuning assert type(result) is Hamiltonian - assert result.terms == [default_shifting_field, default_shifting_field] + assert result.terms == [default_local_detuning, default_local_detuning] -def test_iadd_to_other(default_shifting_field): - expected = [Mock(), Mock(), Mock(), default_shifting_field] +def test_iadd_to_other(default_local_detuning): + expected = [Mock(), Mock(), Mock(), default_local_detuning] other = Hamiltonian([expected[0], expected[1], expected[2]]) other += expected[3] assert other.terms == expected @@ -69,7 +69,7 @@ def test_from_lists(): glob_amplitude = [0.5, 0.8, 0.9, 1.0] pattern = [0.3, 0.7, 0.6, -0.5, 0, 1.6] - sh_field = ShiftingField.from_lists(times, glob_amplitude, pattern) + sh_field = LocalDetuning.from_lists(times, glob_amplitude, pattern) assert sh_field.magnitude.time_series.values() == glob_amplitude assert sh_field.magnitude.pattern.series == pattern @@ -82,7 +82,7 @@ def test_from_lists_not_eq_length(): glob_amplitude = [0.5, 0.8, 0.9, 1.0] pattern = [0.3, 0.7, 0.6, -0.5, 0, 1.6] - ShiftingField.from_lists(times, glob_amplitude, pattern) + LocalDetuning.from_lists(times, glob_amplitude, pattern) def test_stitch(): @@ -94,8 +94,8 @@ def test_stitch(): glob_amplitude_2 = [0.5, 0.8, 0.9, 1.0] pattern_2 = pattern_1 - sh_field_1 = ShiftingField.from_lists(times_1, glob_amplitude_1, pattern_1) - sh_field_2 = ShiftingField.from_lists(times_2, glob_amplitude_2, pattern_2) + sh_field_1 = LocalDetuning.from_lists(times_1, glob_amplitude_1, pattern_1) + sh_field_2 = LocalDetuning.from_lists(times_2, glob_amplitude_2, pattern_2) new_sh_field = sh_field_1.stitch(sh_field_2, boundary=StitchBoundaryCondition.LEFT) @@ -116,8 +116,8 @@ def test_stitch_not_eq_pattern(): glob_amplitude_2 = [0.5, 0.8, 0.9, 1.0] pattern_2 = [-0.3, 0.7, 0.6, -0.5, 0, 1.6] - sh_field_1 = ShiftingField.from_lists(times_1, glob_amplitude_1, pattern_1) - sh_field_2 = ShiftingField.from_lists(times_2, glob_amplitude_2, pattern_2) + sh_field_1 = LocalDetuning.from_lists(times_1, glob_amplitude_1, pattern_1) + sh_field_2 = LocalDetuning.from_lists(times_2, glob_amplitude_2, pattern_2) sh_field_1.stitch(sh_field_2) @@ -125,7 +125,7 @@ def test_stitch_not_eq_pattern(): def test_discretize(): magnitude_mock = Mock() mock_properties = Mock() - field = ShiftingField(magnitude=magnitude_mock) + field = LocalDetuning(magnitude=magnitude_mock) discretized_field = field.discretize(mock_properties) magnitude_mock.discretize.assert_called_with( time_resolution=mock_properties.rydberg.rydbergLocal.timeResolution, @@ -137,5 +137,5 @@ def test_discretize(): @pytest.mark.xfail(raises=ValueError) -def test_iadd_to_itself(default_shifting_field): - default_shifting_field += Hamiltonian(Mock()) +def test_iadd_to_itself(default_local_detuning): + default_local_detuning += Hamiltonian(Mock()) diff --git a/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py b/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py index 23cca78be..be2d6fdbe 100644 --- a/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py +++ b/test/unit_tests/braket/tasks/test_analog_hamiltonian_simulation_task_result.py @@ -75,7 +75,7 @@ def additional_metadata(): }, } ], - "shiftingFields": [ + "localDetuning": [ { "magnitude": { "time_series": { From da70fffe982ce6fdc2efb1682290f1cabc42aed7 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 22:54:53 +0000 Subject: [PATCH 67/98] prepare release v1.77.2 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9384714f1..ca418c447 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.2 (2024-04-10) + +### Bug Fixes and Other Changes + + * remove shifting field from testing + ## v1.77.1 (2024-04-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 7d0928a71..279cf6894 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.2.dev0" +__version__ = "1.77.2" From 04d8a957c88fa6d2a6f9ecdf48d940a0d33fef1a Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 10 Apr 2024 22:54:53 +0000 Subject: [PATCH 68/98] update development version to v1.77.3.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 279cf6894..5395acb5e 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.2" +__version__ = "1.77.3.dev0" From 18aa64e880818f5d7c15575728e427b9fa7cb677 Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Thu, 11 Apr 2024 08:37:13 -0700 Subject: [PATCH 69/98] fix: measure target qubits are required (#940) * fix: measure target qubits are required * removed commented out code * fix test name that changed during merge conflict resolution * convert non iterable targets to QubitSet, reformat conditions, add test for target_mapping to increase coverage * move check for already measured target qubits to check_if_qubit_measured function * simplify previously measured qubit error message * pull in the latest default-simulator version * Apply suggestions from code review Co-authored-by: Cody Wang * Update src/braket/circuits/circuit.py * Apply suggestions from code review --------- Co-authored-by: Cody Wang --- setup.py | 2 +- src/braket/circuits/circuit.py | 82 ++++++------------- src/braket/circuits/moments.py | 6 +- .../circuits/test_ascii_circuit_diagram.py | 21 +++++ .../braket/circuits/test_circuit.py | 80 ++++++++++++++---- .../circuits/test_unicode_circuit_diagram.py | 22 +++++ 6 files changed, 137 insertions(+), 76 deletions(-) diff --git a/setup.py b/setup.py index 821748ef3..12bec6f07 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ package_dir={"": "src"}, install_requires=[ "amazon-braket-schemas>=1.21.3", - "amazon-braket-default-simulator>=1.21.2", + "amazon-braket-default-simulator>=1.21.4", "oqpy~=0.3.5", "backoff", "boltons", diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index a4b48629d..9bac39a6c 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -440,22 +440,17 @@ def _check_if_qubit_measured( Raises: ValueError: If adding a gate or noise operation after a measure instruction. """ - if ( - # check if there is a measure instruction on the target qubit - target - and target in self._measure_targets - # check if there is a measure instruction on any qubits in the target_mapping - or (target_mapping and any(targ in self._measure_targets for targ in target_mapping)) - # If no target or target_mapping is supplied, check if there is a measure - # instruction on the current instructions target qubit - or ( - instruction.target - and any(targ in self._measure_targets for targ in instruction.target) - ) - ): - raise ValueError( - "cannot add a gate or noise operation on a qubit after a measure instruction." + if self._measure_targets: + measure_on_target_mapping = target_mapping and any( + targ in self._measure_targets for targ in target_mapping.values() ) + if ( + # check if there is a measure instruction on the targeted qubit(s) + measure_on_target_mapping + or any(tar in self._measure_targets for tar in QubitSet(target)) + or any(tar in self._measure_targets for tar in QubitSet(instruction.target)) + ): + raise ValueError("cannot apply instruction to measured qubits.") def add_instruction( self, @@ -510,8 +505,7 @@ def add_instruction( raise TypeError("Only one of 'target_mapping' or 'target' can be supplied.") # Check if there is a measure instruction on the circuit - if not isinstance(instruction.operator, Measure) and self._measure_targets: - self._check_if_qubit_measured(instruction, target, target_mapping) + self._check_if_qubit_measured(instruction, target, target_mapping) if not target_mapping and not target: # Nothing has been supplied, add instruction @@ -724,13 +718,12 @@ def _add_measure(self, target_qubits: QubitSetInput) -> None: else: self._measure_targets = [target] - def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: + def measure(self, target_qubits: QubitSetInput) -> Circuit: """ Add a `measure` operator to `self` ensuring only the target qubits are measured. Args: - target_qubits (QubitSetInput | None): target qubits to measure. - Default=None + target_qubits (QubitSetInput): target qubits to measure. Returns: Circuit: self @@ -750,47 +743,21 @@ def measure(self, target_qubits: QubitSetInput | None = None) -> Circuit: Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(2)]), Instruction('operator': Measure, 'target': QubitSet([Qubit(0)])] """ - # check whether measuring an empty circuit - if not self.qubits: - raise IndexError("cannot measure an empty circuit.") - - if isinstance(target_qubits, int): - target_qubits = [target_qubits] + if not isinstance(target_qubits, Iterable): + target_qubits = QubitSet(target_qubits) # Check if result types are added on the circuit if self.result_types: raise ValueError("a circuit cannot contain both measure instructions and result types.") - if target_qubits: - # Check if the target_qubits are already measured - if self._measure_targets and any( - target in self._measure_targets for target in target_qubits - ): - intersection = set(target_qubits) & set(self._measure_targets) - raise ValueError( - f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " - "more than once." - ) - # Check if there are repeated qubits in the same measurement - if len(target_qubits) != len(set(target_qubits)): - intersection = [ - qubit for qubit, count in Counter(target_qubits).items() if count > 1 - ] - raise ValueError( - f"cannot repeat qubit(s) {', '.join(map(str, intersection))} " - "in the same measurement." - ) - self._add_measure(target_qubits=target_qubits) - else: - # Check if any qubits are already measured - if self._measure_targets: - intersection = set(self.qubits) & set(self._measure_targets) - raise ValueError( - f"cannot measure the same qubit(s) {', '.join(map(str, intersection))} " - "more than once." - ) - # Measure all the qubits - self._add_measure(target_qubits=self.qubits) + # Check if there are repeated qubits in the same measurement + if len(target_qubits) != len(set(target_qubits)): + intersection = [qubit for qubit, count in Counter(target_qubits).items() if count > 1] + raise ValueError( + f"cannot repeat qubit(s) {', '.join(map(str, intersection))} " + "in the same measurement." + ) + self._add_measure(target_qubits=target_qubits) return self @@ -916,6 +883,9 @@ def apply_gate_noise( if not all(qubit in self.qubits for qubit in target_qubits): raise IndexError("target_qubits must be within the range of the current circuit.") + # Check if there is a measure instruction on the circuit + self._check_if_qubit_measured(instruction=noise, target=target_qubits) + # make noise a list noise = wrap_with_list(noise) diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 031c2f5e5..6622b3467 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -257,6 +257,7 @@ def sort_moments(self) -> None: key_readout_noise = [] moment_copy = OrderedDict() sorted_moment = OrderedDict() + last_measure = self._depth for key, instruction in self._moments.items(): moment_copy[key] = instruction @@ -264,6 +265,9 @@ def sort_moments(self) -> None: key_readout_noise.append(key) elif key.moment_type == MomentType.INITIALIZATION_NOISE: key_initialization_noise.append(key) + elif key.moment_type == MomentType.MEASURE: + last_measure = key.time + key_noise.append(key) else: key_noise.append(key) @@ -272,7 +276,7 @@ def sort_moments(self) -> None: for key in key_noise: sorted_moment[key] = moment_copy[key] # find the max time in the circuit and make it the time for readout noise - max_time = max(self._depth - 1, 0) + max_time = max(last_measure - 1, 0) for key in key_readout_noise: sorted_moment[ diff --git a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py index 7724d9077..aec875568 100644 --- a/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_ascii_circuit_diagram.py @@ -20,6 +20,7 @@ FreeParameter, Gate, Instruction, + Noise, Observable, Operator, ) @@ -935,3 +936,23 @@ def test_measure_multiple_instructions_after(): "T : |0|1|2|3|4|5|6|", ) _assert_correct_diagram(circ, expected) + + +def test_measure_with_readout_noise(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .apply_readout_noise(Noise.BitFlip(probability=0.1), target_qubits=1) + .measure([0, 1]) + ) + expected = ( + "T : |0| 1 |2|", + " ", + "q0 : -H-C---------M-", + " | ", + "q1 : ---X-BF(0.1)-M-", + "", + "T : |0| 1 |2|", + ) + _assert_correct_diagram(circ, expected) diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index d7f6568d4..b29f3b995 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -24,6 +24,7 @@ Gate, Instruction, Moments, + Noise, Observable, QubitSet, ResultType, @@ -670,22 +671,26 @@ def test_measure_qubits_out_of_range(): def test_measure_empty_circuit(): - with pytest.raises(IndexError): - Circuit().measure() - - -def test_measure_no_target(): - circ = Circuit().h(0).cnot(0, 1).measure() + circ = Circuit().measure([0, 1, 2]) expected = ( Circuit() - .add_instruction(Instruction(Gate.H(), 0)) - .add_instruction(Instruction(Gate.CNot(), [0, 1])) .add_instruction(Instruction(Measure(), 0)) .add_instruction(Instruction(Measure(), 1)) + .add_instruction(Instruction(Measure(), 2)) ) assert circ == expected +def test_measure_target_input(): + message = "Supplied qubit index, 1.1, must be an integer." + with pytest.raises(TypeError, match=message): + Circuit().h(0).cnot(0, 1).measure(1.1) + + message = "Supplied qubit index, a, must be an integer." + with pytest.raises(TypeError, match=message): + Circuit().h(0).cnot(0, 1).measure(FreeParameter("a")) + + def test_measure_with_result_types(): message = "a circuit cannot contain both measure instructions and result types." with pytest.raises(ValueError, match=message): @@ -713,13 +718,15 @@ def test_measure_with_multiple_measures(): def test_measure_same_qubit_twice(): - message = "cannot measure the same qubit\\(s\\) 0 more than once." + # message = "cannot measure the same qubit\\(s\\) Qubit\\(0\\) more than once." + message = "cannot apply instruction to measured qubits." with pytest.raises(ValueError, match=message): Circuit().h(0).cnot(0, 1).measure(0).measure(1).measure(0) def test_measure_same_qubit_twice_with_list(): - message = "cannot measure the same qubit\\(s\\) 0 more than once." + # message = "cannot measure the same qubit\\(s\\) Qubit\\(0\\) more than once." + message = "cannot apply instruction to measured qubits." with pytest.raises(ValueError, match=message): Circuit().h(0).cnot(0, 1).measure(0).measure([0, 1]) @@ -730,20 +737,56 @@ def test_measure_same_qubit_twice_with_one_measure(): Circuit().h(0).cnot(0, 1).measure([0, 0, 0]) -def test_measure_empty_measure_after_measure_with_targets(): - message = "cannot measure the same qubit\\(s\\) 0, 1 more than once." +def test_measure_gate_after(): + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." with pytest.raises(ValueError, match=message): - Circuit().h(0).cnot(0, 1).cnot(1, 2).measure(0).measure(1).measure() + Circuit().h(0).measure(0).h([0, 1]) + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." + with pytest.raises(ValueError, match=message): + instr = Instruction(Gate.CNot(), [0, 1]) + Circuit().measure([0, 1]).add_instruction(instr, target_mapping={0: 0, 1: 1}) -def test_measure_gate_after(): - message = "cannot add a gate or noise operation on a qubit after a measure instruction." + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." with pytest.raises(ValueError, match=message): - Circuit().h(0).measure(0).h([0, 1]) + instr = Instruction(Gate.CNot(), [0, 1]) + Circuit().h(0).measure(0).add_instruction(instr, target=[0, 1]) + + +def test_measure_noise_after(): + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." + with pytest.raises(ValueError, match=message): + Circuit().h(1).h(1).h(2).h(5).h(4).h(3).cnot(1, 2).measure([0, 1, 2, 3, 4]).kraus( + targets=[0], matrices=[np.array([[1, 0], [0, 1]])] + ) + + +def test_measure_with_readout_noise(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .apply_readout_noise(Noise.BitFlip(probability=0.1), target_qubits=1) + .measure([0, 1]) + ) + expected = ( + Circuit() + .add_instruction(Instruction(Gate.H(), 0)) + .add_instruction(Instruction(Gate.CNot(), [0, 1])) + .apply_readout_noise(Noise.BitFlip(probability=0.1), target_qubits=1) + .add_instruction(Instruction(Measure(), 0)) + .add_instruction(Instruction(Measure(), 1)) + ) + assert circ == expected def test_measure_gate_after_with_target_mapping(): - message = "cannot add a gate or noise operation on a qubit after a measure instruction." + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." instr = Instruction(Gate.CNot(), [0, 1]) with pytest.raises(ValueError, match=message): Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction( @@ -752,7 +795,8 @@ def test_measure_gate_after_with_target_mapping(): def test_measure_gate_after_with_target(): - message = "cannot add a gate or noise operation on a qubit after a measure instruction." + # message = "cannot add a gate or noise operation on a qubit after a measure instruction." + message = "cannot apply instruction to measured qubits." instr = Instruction(Gate.CNot(), [0, 1]) with pytest.raises(ValueError, match=message): Circuit().h(0).cnot(0, 1).cnot(1, 2).measure([0, 1]).add_instruction(instr, target=[10, 11]) diff --git a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py index f78b4e746..268c53a96 100644 --- a/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py +++ b/test/unit_tests/braket/circuits/test_unicode_circuit_diagram.py @@ -19,6 +19,7 @@ FreeParameter, Gate, Instruction, + Noise, Observable, Operator, UnicodeCircuitDiagram, @@ -1097,3 +1098,24 @@ def test_measure_multiple_instructions_after(): "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │", ) _assert_correct_diagram(circ, expected) + + +def test_measure_with_readout_noise(): + circ = ( + Circuit() + .h(0) + .cnot(0, 1) + .apply_readout_noise(Noise.BitFlip(probability=0.1), target_qubits=1) + .measure([0, 1]) + ) + expected = ( + "T : │ 0 │ 1 │ 2 │", + " ┌───┐ ┌───┐ ", + "q0 : ─┤ H ├───●───────────────┤ M ├─", + " └───┘ │ └───┘ ", + " ┌─┴─┐ ┌─────────┐ ┌───┐ ", + "q1 : ───────┤ X ├─┤ BF(0.1) ├─┤ M ├─", + " └───┘ └─────────┘ └───┘ ", + "T : │ 0 │ 1 │ 2 │", + ) + _assert_correct_diagram(circ, expected) From 7831de00732f8cc6cf5455396e72524c50694ad4 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Apr 2024 16:16:32 +0000 Subject: [PATCH 70/98] prepare release v1.77.3 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca418c447..6449e1bea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.3 (2024-04-11) + +### Bug Fixes and Other Changes + + * measure target qubits are required + ## v1.77.2 (2024-04-10) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5395acb5e..5fd378c39 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.3.dev0" +__version__ = "1.77.3" From 53aba5eb8f8fed19280c96e980844663f7fc5f92 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 11 Apr 2024 16:16:32 +0000 Subject: [PATCH 71/98] update development version to v1.77.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5fd378c39..292d3ff75 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.3" +__version__ = "1.77.4.dev0" From b1cd32da7d0c24bec2a0f7a5cf04b1b3f08bf6e3 Mon Sep 17 00:00:00 2001 From: Ryan Shaffer <3620100+rmshaffer@users.noreply.github.com> Date: Fri, 12 Apr 2024 10:32:38 -0400 Subject: [PATCH 72/98] doc: correct gphase matrix representation (#946) --- src/braket/circuits/gates.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 3bfb16731..84ff3de05 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -220,9 +220,8 @@ class GPhase(AngledGate): Unitary matrix: - .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I = \begin{bmatrix} - e^{i \gamma} & 0 \\ - 0 & e^{i \gamma} \end{bmatrix}. + .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I_1 = \begin{bmatrix} + e^{i \gamma} \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): angle in radians. @@ -279,9 +278,8 @@ def gphase( Unitary matrix: - .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I = \begin{bmatrix} - e^{i \gamma} & 0 \\ - 0 & e^{i \gamma} \end{bmatrix}. + .. math:: \mathtt{gphase}(\gamma) = e^{i \gamma} I_1 = \begin{bmatrix} + e^{i \gamma} \end{bmatrix}. Args: angle (Union[FreeParameterExpression, float]): Phase in radians. From a18d445dce4039c9756c26f93e731f4922ab8225 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 15 Apr 2024 16:17:24 +0000 Subject: [PATCH 73/98] prepare release v1.77.3.post0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6449e1bea..c3956a4da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.3.post0 (2024-04-15) + +### Documentation Changes + + * correct gphase matrix representation + ## v1.77.3 (2024-04-11) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 292d3ff75..8bdbbfafd 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.4.dev0" +__version__ = "1.77.3.post0" From 6e9a2dcb10d0b29c5458985873a2bea127582351 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 15 Apr 2024 16:17:24 +0000 Subject: [PATCH 74/98] update development version to v1.77.4.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 8bdbbfafd..292d3ff75 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.3.post0" +__version__ = "1.77.4.dev0" From 5ed6590299584bc2aeefd860a21225ff4c04a168 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:35:54 -0700 Subject: [PATCH 75/98] fix: discretize method now takes None as an arg (#950) --- src/braket/ahs/driving_field.py | 12 +++++++--- src/braket/ahs/field.py | 16 ++++--------- src/braket/ahs/local_detuning.py | 11 +++++---- src/braket/ahs/pattern.py | 12 +++++++--- src/braket/timings/time_series.py | 24 +++++++++++++------ test/unit_tests/braket/ahs/test_field.py | 18 +++++--------- test/unit_tests/braket/ahs/test_pattern.py | 22 ++++++++++++++++- .../braket/timings/test_time_series.py | 21 ++++++++++------ 8 files changed, 87 insertions(+), 49 deletions(-) diff --git a/src/braket/ahs/driving_field.py b/src/braket/ahs/driving_field.py index f6c6430f2..cbf01838d 100644 --- a/src/braket/ahs/driving_field.py +++ b/src/braket/ahs/driving_field.py @@ -122,17 +122,23 @@ def discretize(self, properties: DiscretizationProperties) -> DrivingField: """ driving_parameters = properties.rydberg.rydbergGlobal time_resolution = driving_parameters.timeResolution + + amplitude_value_resolution = driving_parameters.rabiFrequencyResolution discretized_amplitude = self.amplitude.discretize( time_resolution=time_resolution, - value_resolution=driving_parameters.rabiFrequencyResolution, + value_resolution=amplitude_value_resolution, ) + + phase_value_resolution = driving_parameters.phaseResolution discretized_phase = self.phase.discretize( time_resolution=time_resolution, - value_resolution=driving_parameters.phaseResolution, + value_resolution=phase_value_resolution, ) + + detuning_value_resolution = driving_parameters.detuningResolution discretized_detuning = self.detuning.discretize( time_resolution=time_resolution, - value_resolution=driving_parameters.detuningResolution, + value_resolution=detuning_value_resolution, ) return DrivingField( amplitude=discretized_amplitude, phase=discretized_phase, detuning=discretized_detuning diff --git a/src/braket/ahs/field.py b/src/braket/ahs/field.py index 9a473fd99..1522b9d65 100644 --- a/src/braket/ahs/field.py +++ b/src/braket/ahs/field.py @@ -16,7 +16,6 @@ from decimal import Decimal from typing import Optional -from braket.ahs.discretization_types import DiscretizationError from braket.ahs.pattern import Pattern from braket.timings.time_series import TimeSeries @@ -44,8 +43,8 @@ def pattern(self) -> Optional[Pattern]: def discretize( self, - time_resolution: Decimal, - value_resolution: Decimal, + time_resolution: Optional[Decimal] = None, + value_resolution: Optional[Decimal] = None, pattern_resolution: Optional[Decimal] = None, ) -> Field: """Creates a discretized version of the field, @@ -53,24 +52,17 @@ def discretize( closest multiple of their corresponding resolutions. Args: - time_resolution (Decimal): Time resolution - value_resolution (Decimal): Value resolution + time_resolution (Optional[Decimal]): Time resolution + value_resolution (Optional[Decimal]): Value resolution pattern_resolution (Optional[Decimal]): Pattern resolution Returns: Field: A new discretized field. - - Raises: - ValueError: if pattern_resolution is None, but there is a Pattern """ discretized_time_series = self.time_series.discretize(time_resolution, value_resolution) if self.pattern is None: discretized_pattern = None else: - if pattern_resolution is None: - raise DiscretizationError( - f"{self.pattern} is defined but has no pattern_resolution defined" - ) discretized_pattern = self.pattern.discretize(pattern_resolution) discretized_field = Field(time_series=discretized_time_series, pattern=discretized_pattern) return discretized_field diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 574b985d3..1906ce837 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -152,10 +152,13 @@ def discretize(self, properties: DiscretizationProperties) -> LocalDetuning: Returns: LocalDetuning: A new discretized LocalDetuning. """ - shifting_parameters = properties.rydberg.rydbergLocal + local_detuning_parameters = properties.rydberg.rydbergLocal + time_resolution = local_detuning_parameters.timeResolution + value_resolution = local_detuning_parameters.commonDetuningResolution + pattern_resolution = local_detuning_parameters.localDetuningResolution discretized_magnitude = self.magnitude.discretize( - time_resolution=shifting_parameters.timeResolution, - value_resolution=shifting_parameters.commonDetuningResolution, - pattern_resolution=shifting_parameters.localDetuningResolution, + time_resolution=time_resolution, + value_resolution=value_resolution, + pattern_resolution=pattern_resolution, ) return LocalDetuning(discretized_magnitude) diff --git a/src/braket/ahs/pattern.py b/src/braket/ahs/pattern.py index 462f0e369..92637fe0f 100644 --- a/src/braket/ahs/pattern.py +++ b/src/braket/ahs/pattern.py @@ -15,6 +15,7 @@ from decimal import Decimal from numbers import Number +from typing import Optional class Pattern: @@ -34,16 +35,21 @@ def series(self) -> list[Number]: """ return self._series - def discretize(self, resolution: Decimal) -> Pattern: + def discretize(self, resolution: Optional[Decimal]) -> Pattern: """Creates a discretized version of the pattern, where each value is rounded to the closest multiple of the resolution. Args: - resolution (Decimal): Resolution of the discretization + resolution (Optional[Decimal]): Resolution of the discretization Returns: Pattern: The new discretized pattern """ - discretized_series = [round(Decimal(num) / resolution) * resolution for num in self.series] + if resolution is None: + discretized_series = [Decimal(num) for num in self.series] + else: + discretized_series = [ + round(Decimal(num) / resolution) * resolution for num in self.series + ] return Pattern(series=discretized_series) diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index a558bec71..afdabd726 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -19,6 +19,7 @@ from decimal import Decimal from enum import Enum from numbers import Number +from typing import Optional @dataclass @@ -267,24 +268,33 @@ def stitch( return new_time_series - def discretize(self, time_resolution: Decimal, value_resolution: Decimal) -> TimeSeries: + def discretize( + self, time_resolution: Optional[Decimal], value_resolution: Optional[Decimal] + ) -> TimeSeries: """Creates a discretized version of the time series, rounding all times and values to the closest multiple of the corresponding resolution. Args: - time_resolution (Decimal): Time resolution - value_resolution (Decimal): Value resolution + time_resolution (Optional[Decimal]): Time resolution + value_resolution (Optional[Decimal]): Value resolution Returns: TimeSeries: A new discretized time series. """ discretized_ts = TimeSeries() for item in self: - discretized_ts.put( - time=round(Decimal(item.time) / time_resolution) * time_resolution, - value=round(Decimal(item.value) / value_resolution) * value_resolution, - ) + if time_resolution is None: + discretized_time = Decimal(item.time) + else: + discretized_time = round(Decimal(item.time) / time_resolution) * time_resolution + + if value_resolution is None: + discretized_value = Decimal(item.value) + else: + discretized_value = round(Decimal(item.value) / value_resolution) * value_resolution + + discretized_ts.put(time=discretized_time, value=discretized_value) return discretized_ts @staticmethod diff --git a/test/unit_tests/braket/ahs/test_field.py b/test/unit_tests/braket/ahs/test_field.py index 4212ba336..2ff6714ce 100644 --- a/test/unit_tests/braket/ahs/test_field.py +++ b/test/unit_tests/braket/ahs/test_field.py @@ -16,7 +16,6 @@ import pytest -from braket.ahs.discretization_types import DiscretizationError from braket.ahs.field import Field from braket.ahs.pattern import Pattern from braket.timings.time_series import TimeSeries @@ -80,6 +79,12 @@ def test_discretize( [ (Decimal("0.1"), Decimal("10"), Decimal("0.5")), (Decimal("10"), Decimal("20"), None), + (Decimal("0.1"), None, Decimal("0.5")), + (None, Decimal("10"), Decimal("0.5")), + (None, None, Decimal("0.5")), + (None, Decimal("10"), None), + (Decimal("0.1"), None, None), + (None, None, None), (Decimal("100"), Decimal("0.1"), Decimal("1")), ], ) @@ -93,14 +98,3 @@ def test_uniform_field( ) or expected.pattern.series == actual.pattern.series assert expected.time_series.times() == actual.time_series.times() assert expected.time_series.values() == actual.time_series.values() - - -@pytest.mark.parametrize( - "time_res, value_res, pattern_res", - [ - (Decimal("10"), Decimal("20"), None), - ], -) -@pytest.mark.xfail(raises=DiscretizationError) -def test_invalid_pattern_res(default_field, time_res, value_res, pattern_res): - default_field.discretize(time_res, value_res, pattern_res) diff --git a/test/unit_tests/braket/ahs/test_pattern.py b/test/unit_tests/braket/ahs/test_pattern.py index d84f3a925..920f2cc29 100644 --- a/test/unit_tests/braket/ahs/test_pattern.py +++ b/test/unit_tests/braket/ahs/test_pattern.py @@ -20,7 +20,15 @@ @pytest.fixture def default_values(): - return [0, 0.1, 1, 0.5, 0.2, 0.001, 1e-10] + return [ + Decimal(0), + Decimal("0.1"), + Decimal(1), + Decimal("0.5"), + Decimal("0.2"), + Decimal("0.001"), + Decimal("1e-10"), + ] @pytest.fixture @@ -38,6 +46,18 @@ def test_create(): "res, expected_series", [ # default pattern: [0, 0.1, 1, 0.5, 0.2, 0.001, 1e-10] + ( + None, + [ + Decimal("0"), + Decimal("0.1"), + Decimal("1"), + Decimal("0.5"), + Decimal("0.2"), + Decimal("0.001"), + Decimal("1e-10"), + ], + ), ( Decimal("0.001"), [ diff --git a/test/unit_tests/braket/timings/test_time_series.py b/test/unit_tests/braket/timings/test_time_series.py index 0f99ca650..c5a26334f 100755 --- a/test/unit_tests/braket/timings/test_time_series.py +++ b/test/unit_tests/braket/timings/test_time_series.py @@ -20,7 +20,12 @@ @pytest.fixture def default_values(): - return [(2700, 25.1327), (300, 25.1327), (600, 15.1327), (Decimal(0.3), Decimal(0.4))] + return [ + (2700, Decimal("25.1327")), + (300, Decimal("25.1327")), + (600, Decimal("15.1327")), + (Decimal("0.3"), Decimal("0.4")), + ] @pytest.fixture @@ -265,11 +270,12 @@ def test_stitch_wrong_bndry_value(): @pytest.mark.parametrize( "time_res, expected_times", [ - # default_time_series: [(Decimal(0.3), Decimal(0.4), (300, 25.1327), (600, 15.1327), (2700, 25.1327))] # noqa - (Decimal(0.5), [Decimal("0.5"), Decimal("300"), Decimal("600"), Decimal("2700")]), - (Decimal(1), [Decimal("0"), Decimal("300"), Decimal("600"), Decimal("2700")]), - (Decimal(200), [Decimal("0"), Decimal("400"), Decimal("600"), Decimal("2800")]), - (Decimal(1000), [Decimal("0"), Decimal("1000"), Decimal("3000")]), + # default_time_series: [(Decimal(0.3), Decimal(0.4)), (300, 25.1327), (600, 15.1327), (2700, 25.1327)] # noqa + (None, [Decimal("0.3"), Decimal("300"), Decimal("600"), Decimal("2700")]), + (Decimal("0.5"), [Decimal("0.5"), Decimal("300"), Decimal("600"), Decimal("2700")]), + (Decimal("1"), [Decimal("0"), Decimal("300"), Decimal("600"), Decimal("2700")]), + (Decimal("200"), [Decimal("0"), Decimal("400"), Decimal("600"), Decimal("2800")]), + (Decimal("1000"), [Decimal("0"), Decimal("1000"), Decimal("3000")]), ], ) def test_discretize_times(default_time_series, time_res, expected_times): @@ -280,7 +286,8 @@ def test_discretize_times(default_time_series, time_res, expected_times): @pytest.mark.parametrize( "value_res, expected_values", [ - # default_time_series: [(Decimal(0.3), Decimal(0.4), (300, 25.1327), (600, 15.1327), (2700, 25.1327))] # noqa + # default_time_series: [(Decimal(0.3), Decimal(0.4)), (300, 25.1327), (600, 15.1327), (2700, 25.1327)] # noqa + (None, [Decimal("0.4"), Decimal("25.1327"), Decimal("15.1327"), Decimal("25.1327")]), (Decimal("0.1"), [Decimal("0.4"), Decimal("25.1"), Decimal("15.1"), Decimal("25.1")]), (Decimal(1), [Decimal("0"), Decimal("25"), Decimal("15"), Decimal("25")]), (Decimal(6), [Decimal("0"), Decimal("24"), Decimal("18"), Decimal("24")]), From 0a90fa6b6ed55335ee43de0c28585318b18fe622 Mon Sep 17 00:00:00 2001 From: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> Date: Mon, 15 Apr 2024 18:19:15 -0400 Subject: [PATCH 76/98] doc: Correct miscellaneous spelling mistakes in docstrings (#952) --- CHANGELOG.md | 2 +- doc/examples-adv-circuits-algorithms.rst | 2 +- doc/examples-braket-features.rst | 2 +- src/braket/annealing/problem.py | 2 +- src/braket/aws/aws_device.py | 2 +- src/braket/aws/aws_quantum_task.py | 2 +- src/braket/aws/aws_quantum_task_batch.py | 2 +- src/braket/aws/aws_session.py | 4 ++-- src/braket/circuits/circuit.py | 2 +- src/braket/circuits/gate_calibrations.py | 2 +- src/braket/circuits/moments.py | 2 +- src/braket/circuits/noise.py | 2 +- src/braket/circuits/noise_helpers.py | 8 ++++---- .../circuits/noise_model/qubit_initialization_criteria.py | 2 +- src/braket/circuits/quantum_operator_helpers.py | 2 +- .../text_diagram_builders/ascii_circuit_diagram.py | 2 +- .../text_diagram_builders/text_circuit_diagram.py | 4 ++-- .../text_diagram_builders/unicode_circuit_diagram.py | 2 +- src/braket/jobs/local/local_job_container.py | 2 +- .../analog_hamiltonian_simulation_quantum_task_result.py | 2 +- src/braket/timings/time_series.py | 2 +- src/braket/tracking/tracker.py | 4 ++-- 22 files changed, 28 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3956a4da..8640a1db6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ ### Bug Fixes and Other Changes - * backwards compatiblity for local detuning + * backwards compatibility for local detuning ## v1.76.1 (2024-04-08) diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index b37e87e34..4a16c9cd6 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -2,7 +2,7 @@ Advanced circuits and algorithms ################################ -Learn more about working with advanced circuits and algoritms. +Learn more about working with advanced circuits and algorithms. .. toctree:: :maxdepth: 2 diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index 75361f172..004e2bfe3 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -27,7 +27,7 @@ selection for your circuits manually, when running on QPUs. *************************************************************************************************************************************************************************************************** This example shows how to interact with the Amazon Braket GetDevice API to -retrieve Amazon Braket devices (such as simulators and QPUs) programatically, +retrieve Amazon Braket devices (such as simulators and QPUs) programmatically, and how to gain access to their properties. *********************************************************************************************************************************************************************************** diff --git a/src/braket/annealing/problem.py b/src/braket/annealing/problem.py index d55de4e6e..9515cbfa6 100644 --- a/src/braket/annealing/problem.py +++ b/src/braket/annealing/problem.py @@ -39,7 +39,7 @@ def __init__( linear: dict[int, float] | None = None, quadratic: dict[tuple[int, int], float] | None = None, ): - """Initialzes a `Problem`. + """Initializes a `Problem`. Args: problem_type (ProblemType): The type of annealing problem diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 43780aa38..390145aec 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -819,7 +819,7 @@ def _parse_calibration_json( Returns: dict[tuple[Gate, QubitSet], PulseSequence]: The - structured data based on a mapping of `tuple[Gate, Qubit]` to its calibration repesented as a + structured data based on a mapping of `tuple[Gate, Qubit]` to its calibration represented as a `PulseSequence`. """ # noqa: E501 diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index 54f544159..c386469b0 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -320,7 +320,7 @@ def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: dict[str, Any]: The response from the Amazon Braket `GetQuantumTask` operation. If `use_cached_value` is `True`, Amazon Braket is not called and the most recently retrieved value is used, unless `GetQuantumTask` was never called, in which case - it wil still be called to populate the metadata for the first time. + it will still be called to populate the metadata for the first time. """ if not use_cached_value or not self._metadata: self._metadata = self._aws_session.get_quantum_task(self._arn) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 4d0d06b53..964fabe7e 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -432,7 +432,7 @@ def size(self) -> int: @property def unfinished(self) -> set[str]: - """Gets all the IDs of all the quantum tasks in teh batch that have yet to complete. + """Gets all the IDs of all the quantum tasks in the batch that have yet to complete. Returns: set[str]: The IDs of all the quantum tasks in the batch that have yet to complete. diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index d2f2099f4..6a1bd1ce8 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -731,7 +731,7 @@ def describe_log_streams( Would have been received in a previous call. Returns: - dict[str, Any]: Dicionary containing logStreams and nextToken + dict[str, Any]: Dictionary containing logStreams and nextToken """ log_stream_args = { "logGroupName": log_group, @@ -767,7 +767,7 @@ def get_log_events( Would have been received in a previous call. Returns: - dict[str, Any]: Dicionary containing events, nextForwardToken, and nextBackwardToken + dict[str, Any]: Dictionary containing events, nextForwardToken, and nextBackwardToken """ log_events_args = { "logGroupName": log_group, diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 9bac39a6c..bf0ee07d8 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -276,7 +276,7 @@ def add_result_type( Raises: TypeError: If both `target_mapping` and `target` are supplied. - ValueError: If a meaure instruction exists on the current circuit. + ValueError: If a measure instruction exists on the current circuit. Examples: >>> result_type = ResultType.Probability(target=[0, 1]) diff --git a/src/braket/circuits/gate_calibrations.py b/src/braket/circuits/gate_calibrations.py index 7617493cb..69ff66254 100644 --- a/src/braket/circuits/gate_calibrations.py +++ b/src/braket/circuits/gate_calibrations.py @@ -27,7 +27,7 @@ class GateCalibrations: - """An object containing gate calibration data. The data respresents the mapping on a particular gate + """An object containing gate calibration data. The data represents the mapping on a particular gate on a set of qubits to its calibration to be used by a quantum device. This is represented by a dictionary with keys of `Tuple(Gate, QubitSet)` mapped to a `PulseSequence`. """ # noqa: E501 diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 6622b3467..66ea41913 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -290,7 +290,7 @@ def _max_time_for_qubit(self, qubit: Qubit) -> int: return self._max_times.get(qubit, -1) # - # Implement abstract methods, default to calling selfs underlying dictionary + # Implement abstract methods, default to calling `self`'s underlying dictionary # def keys(self) -> KeysView[MomentsKey]: diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index fe256f1da..0cad544e2 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -639,7 +639,7 @@ def __init__( qubit_count: Optional[int], ascii_symbols: Sequence[str], ): - """Initalizes a `DampingNoise`. + """Initializes a `DampingNoise`. Args: gamma (Union[FreeParameterExpression, float]): Probability of damping. diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 6dda8130d..89f30cac8 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -110,7 +110,7 @@ def check_noise_target_qubits( """Helper function to check whether all the target_qubits are positive integers. Args: - circuit (Circuit): A ciruit where `noise` is to be checked. + circuit (Circuit): A circuit where `noise` is to be checked. target_qubits (Optional[QubitSetInput]): Index or indices of qubit(s). Returns: @@ -141,7 +141,7 @@ def apply_noise_to_moments( `target_qubits`. Args: - circuit (Circuit): A ciruit where `noise` is applied to. + circuit (Circuit): A circuit to `noise` is applied to. noise (Iterable[type[Noise]]): Noise channel(s) to be applied to the circuit. target_qubits (QubitSet): Index or indices of qubits. `noise` is applied to. @@ -209,7 +209,7 @@ def _apply_noise_to_gates_helper( Returns: tuple[Iterable[Instruction], int, bool]: A tuple of three values: - new_noise_instruction: A list of noise intructions + new_noise_instruction: A list of noise instructions noise_index: The number of noise channels applied to the gate noise_applied: Whether noise is applied or not """ @@ -248,7 +248,7 @@ def apply_noise_to_gates( the same number of qubits as `noise.qubit_count`. Args: - circuit (Circuit): A ciruit where `noise` is applied to. + circuit (Circuit): A circuit where `noise` is applied to. noise (Iterable[type[Noise]]): Noise channel(s) to be applied to the circuit. target_gates (Union[Iterable[type[Gate]], ndarray]): List of gates, or a unitary matrix diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py index eb8ea0f21..e1790fd21 100644 --- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py +++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.py @@ -54,7 +54,7 @@ def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: Returns: Union[CriteriaKeyResult, set[Any]]: The return value is based on the key type: - QUBIT will return a set of qubit targets that are relevant to this Critera, or + QUBIT will return a set of qubit targets that are relevant to this Criteria, or CriteriaKeyResult.ALL if the Criteria is relevant for all (possible) qubits. All other keys will return an empty set. """ diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 8d64888ed..15cb8d1fd 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -88,7 +88,7 @@ def is_unitary(matrix: np.ndarray) -> bool: def is_cptp(matrices: Iterable[np.ndarray]) -> bool: - """Whether a transformation defined by these matrics as Kraus operators is a + """Whether a transformation defined by these matrices as Kraus operators is a completely positive trace preserving (CPTP) map. This is the requirement for a transformation to be a quantum channel. Reference: Section 8.2.3 in Nielsen & Chuang (2010) 10th edition. diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index 3106afc47..a633d318c 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -179,7 +179,7 @@ def _draw_symbol( Args: symbol (str): the gate name - symbols_width (int): size of the expected output. The ouput will be filled with + symbols_width (int): size of the expected output. The output will be filled with cls._qubit_line_character() if needed. connection (Literal["above", "below", "both", "none"]): character indicating if the gate also involve a qubit with a lower index. diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py index c8bfa2650..e1dda5a3b 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -81,7 +81,7 @@ def _draw_symbol( Args: symbol (str): the gate name - symbols_width (int): size of the expected output. The ouput will be filled with + symbols_width (int): size of the expected output. The output will be filled with cls._qubit_line_character() if needed. connection (Literal["above", "below", "both", "none"]): specifies if a connection will be drawn above and/or below the box. @@ -229,7 +229,7 @@ def _create_output( qubits: QubitSet, global_phase: float | None, ) -> str: - """Creates the ouput for a single column: + """Creates the output for a single column: a. If there was one or more gphase gate, create a first line with the total global phase shift ending with the _vertical_delimiter() class attribute, e.g. 0.14| b. for each qubit, append the text representation produces by cls._draw_symbol diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 9e1779cb7..0739724e9 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -203,7 +203,7 @@ def _draw_symbol( Args: symbol (str): the gate name - symbols_width (int): size of the expected output. The ouput will be filled with + symbols_width (int): size of the expected output. The output will be filled with cls._qubit_line_character() if needed. connection (Literal["above", "below", "both", "none"]): specifies if a connection will be drawn above and/or below the box. diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index c4432dcf7..04aeaff1a 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -45,7 +45,7 @@ def __init__( Default: AwsSession() logger (Logger): Logger object with which to write logs. Default: `getLogger(__name__)` - force_update (bool): Try to update the container, if an update is availble. + force_update (bool): Try to update the container, if an update is available. Default: False """ self._aws_session = aws_session or AwsSession() diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index ade8a0f79..0bc110b2c 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -116,7 +116,7 @@ def get_counts(self) -> dict[str, int]: Returns: dict[str, int]: number of times each state configuration is measured. Returns None if none of shot measurements are successful. - Only succesful shots contribute to the state count. + Only successful shots contribute to the state count. """ state_counts = Counter() states = ["e", "r", "g"] diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index afdabd726..0023f32f5 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -152,7 +152,7 @@ def concatenate(self, other: TimeSeries) -> TimeSeries: Notes: Keeps the time points in both time series unchanged. Assumes that the time points in the first TimeSeries - are at earler times then the time points in the second TimeSeries. + are at earlier times then the time points in the second TimeSeries. Returns: TimeSeries: The concatenated time series. diff --git a/src/braket/tracking/tracker.py b/src/braket/tracking/tracker.py index 2f77eec66..47c13625a 100644 --- a/src/braket/tracking/tracker.py +++ b/src/braket/tracking/tracker.py @@ -122,7 +122,7 @@ def quantum_tasks_statistics(self) -> dict[str, dict[str, Any]]: Returns: dict[str, dict[str, Any]]: A dictionary where each key is a device arn, and maps to - a dictionary sumarizing the quantum tasks run on the device. The summary includes the + a dictionary summarizing the quantum tasks run on the device. The summary includes the total shots sent to the device and the most recent status of the quantum tasks created on this device. For finished quantum tasks on simulator devices, the summary also includes the duration of the simulation. @@ -271,7 +271,7 @@ def _get_simulator_task_cost(task_arn: str, details: dict) -> Decimal: product_family = "Simulator Task" operation = "CompleteTask" if details["status"] == "FAILED" and device_name == "TN1": - # Rehersal step of TN1 can fail and charges still apply. + # Rehearsal step of TN1 can fail and charges still apply. operation = "FailedTask" search_dict = { From 349957779432cf2b4efe02a907db976b197f21e4 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Apr 2024 16:19:17 +0000 Subject: [PATCH 77/98] prepare release v1.77.4 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8640a1db6..4731b0263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.77.4 (2024-04-16) + +### Bug Fixes and Other Changes + + * discretize method now takes None as an arg + +### Documentation Changes + + * Correct miscellaneous spelling mistakes in docstrings + ## v1.77.3.post0 (2024-04-15) ### Documentation Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 292d3ff75..1b29b8f37 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.4.dev0" +__version__ = "1.77.4" From 9388a25b448e91b28edcc19f774c4a41f6ed44f1 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Apr 2024 16:19:17 +0000 Subject: [PATCH 78/98] update development version to v1.77.5.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1b29b8f37..5d3392210 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.4" +__version__ = "1.77.5.dev0" From f69c824da1122ad00640a88fd458ad433c108f11 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 16 Apr 2024 13:34:57 -0700 Subject: [PATCH 79/98] fix: remove optional discretization fields (#953) --- src/braket/ahs/local_detuning.py | 4 ---- .../braket/ahs/test_analog_hamiltonian_simulation.py | 6 ++---- test/unit_tests/braket/ahs/test_local_detuning.py | 2 -- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 1906ce837..39a475922 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -154,11 +154,7 @@ def discretize(self, properties: DiscretizationProperties) -> LocalDetuning: """ local_detuning_parameters = properties.rydberg.rydbergLocal time_resolution = local_detuning_parameters.timeResolution - value_resolution = local_detuning_parameters.commonDetuningResolution - pattern_resolution = local_detuning_parameters.localDetuningResolution discretized_magnitude = self.magnitude.discretize( time_resolution=time_resolution, - value_resolution=value_resolution, - pattern_resolution=pattern_resolution, ) return LocalDetuning(discretized_magnitude) diff --git a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py index 65cc2a074..83178c120 100644 --- a/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py +++ b/test/unit_tests/braket/ahs/test_analog_hamiltonian_simulation.py @@ -141,8 +141,6 @@ def test_discretize(register, driving_field, local_detuning): device.properties.paradigm.rydberg.rydbergGlobal.phaseResolution = Decimal("5E-7") device.properties.paradigm.rydberg.rydbergLocal.timeResolution = Decimal("1E-9") - device.properties.paradigm.rydberg.rydbergLocal.commonDetuningResolution = Decimal("2000.0") - device.properties.paradigm.rydberg.rydbergLocal.localDetuningResolution = Decimal("0.01") discretized_ahs = ahs.discretize(device) discretized_ir = discretized_ahs.to_ir() @@ -179,10 +177,10 @@ def test_discretize(register, driving_field, local_detuning): } local_detuning = discretized_json["hamiltonian"]["localDetuning"][0]["magnitude"] assert local_detuning == { - "pattern": ["0.50", "1.00", "0.50", "0.50", "0.50", "0.50"], + "pattern": ["0.5", "1", "0.5", "0.5", "0.5", "0.5"], "time_series": { "times": ["0E-9", "0.000003000"], - "values": ["-125664000.0", "125664000.0"], + "values": ["-125664000", "125664000"], }, } diff --git a/test/unit_tests/braket/ahs/test_local_detuning.py b/test/unit_tests/braket/ahs/test_local_detuning.py index 4c4f94675..8768dce64 100644 --- a/test/unit_tests/braket/ahs/test_local_detuning.py +++ b/test/unit_tests/braket/ahs/test_local_detuning.py @@ -129,8 +129,6 @@ def test_discretize(): discretized_field = field.discretize(mock_properties) magnitude_mock.discretize.assert_called_with( time_resolution=mock_properties.rydberg.rydbergLocal.timeResolution, - value_resolution=mock_properties.rydberg.rydbergLocal.commonDetuningResolution, - pattern_resolution=mock_properties.rydberg.rydbergLocal.localDetuningResolution, ) assert field is not discretized_field assert discretized_field.magnitude == magnitude_mock.discretize.return_value From f66a21a11785261852e413477ac76ac0296d1ff5 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Apr 2024 20:53:03 +0000 Subject: [PATCH 80/98] prepare release v1.77.5 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4731b0263..2a86ca359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.5 (2024-04-16) + +### Bug Fixes and Other Changes + + * remove optional discretization fields + ## v1.77.4 (2024-04-16) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 5d3392210..6ce89b9b2 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.5.dev0" +__version__ = "1.77.5" From 0e22849e194f2844269eb0f0ddd7c6a06c91c6d7 Mon Sep 17 00:00:00 2001 From: ci Date: Tue, 16 Apr 2024 20:53:03 +0000 Subject: [PATCH 81/98] update development version to v1.77.6.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 6ce89b9b2..15eb83029 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.5" +__version__ = "1.77.6.dev0" From 3698931d5015533f4600cfc7d4c7d856f31c5b42 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:53:40 -0700 Subject: [PATCH 82/98] fix: if rydberg local is not pulled, pass in None (#959) --- src/braket/ahs/local_detuning.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 39a475922..59732d4ff 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -153,7 +153,9 @@ def discretize(self, properties: DiscretizationProperties) -> LocalDetuning: LocalDetuning: A new discretized LocalDetuning. """ local_detuning_parameters = properties.rydberg.rydbergLocal - time_resolution = local_detuning_parameters.timeResolution + time_resolution = ( + local_detuning_parameters.timeResolution if local_detuning_parameters else None + ) discretized_magnitude = self.magnitude.discretize( time_resolution=time_resolution, ) From c3eb8b619c486b7445ed9b43a514775e82385eee Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 17 Apr 2024 18:18:35 +0000 Subject: [PATCH 83/98] prepare release v1.77.6 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a86ca359..6a91e3d8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.77.6 (2024-04-17) + +### Bug Fixes and Other Changes + + * if rydberg local is not pulled, pass in None + ## v1.77.5 (2024-04-16) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 15eb83029..79184888d 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.6.dev0" +__version__ = "1.77.6" From 9bda24493641d8ab783333849dd0268adb214087 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 17 Apr 2024 18:18:35 +0000 Subject: [PATCH 84/98] update development version to v1.77.7.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 79184888d..07aeada44 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.6" +__version__ = "1.77.7.dev0" From 0cd45310542265fc41b517fb11626fdb51b82e37 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Wed, 17 Apr 2024 13:02:09 -0700 Subject: [PATCH 85/98] feat: add phase RX gate (#945) --- .coveragerc | 6 + src/braket/circuits/angled_gate.py | 23 ++-- src/braket/circuits/gates.py | 119 ++++++++++++++++++ src/braket/circuits/translations.py | 1 + .../braket/circuits/test_circuit.py | 1 + test/unit_tests/braket/circuits/test_gates.py | 42 ++++++- 6 files changed, 178 insertions(+), 14 deletions(-) diff --git a/.coveragerc b/.coveragerc index 79545ce2b..933315e86 100644 --- a/.coveragerc +++ b/.coveragerc @@ -23,9 +23,15 @@ exclude_lines = # Have to re-enable the standard pragma pragma: no cover + # Skipping import testing + from importlib.metadata import entry_points + # Don't complain if tests don't hit defensive assertion code: raise NotImplementedError + # Avoid situation where system version causes coverage issues + if sys.version_info.minor == 9: + [html] directory = build/coverage diff --git a/src/braket/circuits/angled_gate.py b/src/braket/circuits/angled_gate.py index 5b4c4faba..49447e58f 100644 --- a/src/braket/circuits/angled_gate.py +++ b/src/braket/circuits/angled_gate.py @@ -433,26 +433,27 @@ def get_angle(gate: AngledGate, **kwargs: FreeParameterExpression | str) -> Angl def _get_angles( - gate: TripleAngledGate, **kwargs: FreeParameterExpression | str -) -> TripleAngledGate: + gate: DoubleAngledGate | TripleAngledGate, **kwargs: FreeParameterExpression | str +) -> DoubleAngledGate | TripleAngledGate: """Gets the angle with all values substituted in that are requested. Args: - gate (TripleAngledGate): The subclass of TripleAngledGate for which the angle is being - obtained. + gate (DoubleAngledGate | TripleAngledGate): The subclass of multi angle AngledGate for + which the angle is being obtained. **kwargs (FreeParameterExpression | str): The named parameters that are being filled for a particular gate. Returns: - TripleAngledGate: A new gate of the type of the AngledGate originally used with all angles - updated. + DoubleAngledGate | TripleAngledGate: A new gate of the type of the AngledGate + originally used with all angles updated. """ - new_angles = [ - ( + angles = [f"angle_{i + 1}" for i in range(len(gate._parameters))] + new_angles_args = { + angle: ( getattr(gate, angle).subs(kwargs) if isinstance(getattr(gate, angle), FreeParameterExpression) else getattr(gate, angle) ) - for angle in ("angle_1", "angle_2", "angle_3") - ] - return type(gate)(angle_1=new_angles[0], angle_2=new_angles[1], angle_3=new_angles[2]) + for angle in angles + } + return type(gate)(**new_angles_args) diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index 84ff3de05..bc4c2f9e9 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -24,6 +24,7 @@ from braket.circuits import circuit from braket.circuits.angled_gate import ( AngledGate, + DoubleAngledGate, TripleAngledGate, _get_angles, _multi_angled_ascii_characters, @@ -3298,6 +3299,124 @@ def gpi( Gate.register_gate(GPi) +class PRx(DoubleAngledGate): + r"""Phase Rx gate. + + Unitary matrix: + + .. math:: \mathtt{PRx}(\theta,\phi) = \begin{bmatrix} + \cos{(\theta / 2)} & -i e^{-i \phi} \sin{(\theta / 2)} \\ + -i e^{i \phi} \sin{(\theta / 2)} & \cos{(\theta / 2)} + \end{bmatrix}. + + Args: + angle_1 (Union[FreeParameterExpression, float]): The first angle of the gate in + radians or expression representation. + angle_2 (Union[FreeParameterExpression, float]): The second angle of the gate in + radians or expression representation. + """ + + def __init__( + self, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + ): + super().__init__( + angle_1=angle_1, + angle_2=angle_2, + qubit_count=None, + ascii_symbols=[_multi_angled_ascii_characters("PRx", angle_1, angle_2)], + ) + + @property + def _qasm_name(self) -> str: + return "prx" + + def to_matrix(self) -> np.ndarray: + """Returns a matrix representation of this gate. + + Returns: + np.ndarray: The matrix representation of this gate. + """ + theta = self.angle_1 + phi = self.angle_2 + return np.array( + [ + [ + np.cos(theta / 2), + -1j * np.exp(-1j * phi) * np.sin(theta / 2), + ], + [ + -1j * np.exp(1j * phi) * np.sin(theta / 2), + np.cos(theta / 2), + ], + ] + ) + + def adjoint(self) -> list[Gate]: + return [PRx(-self.angle_1, self.angle_2)] + + @staticmethod + def fixed_qubit_count() -> int: + return 1 + + def bind_values(self, **kwargs) -> PRx: + return _get_angles(self, **kwargs) + + @staticmethod + @circuit.subroutine(register=True) + def prx( + target: QubitSetInput, + angle_1: Union[FreeParameterExpression, float], + angle_2: Union[FreeParameterExpression, float], + *, + control: Optional[QubitSetInput] = None, + control_state: Optional[BasisStateInput] = None, + power: float = 1, + ) -> Iterable[Instruction]: + r"""PhaseRx gate. + + .. math:: \mathtt{PRx}(\theta,\phi) = \begin{bmatrix} + \cos{(\theta / 2)} & -i e^{-i \phi} \sin{(\theta / 2)} \\ + -i e^{i \phi} \sin{(\theta / 2)} & \cos{(\theta / 2)} + \end{bmatrix}. + + Args: + target (QubitSetInput): Target qubit(s). + angle_1 (Union[FreeParameterExpression, float]): First angle in radians. + angle_2 (Union[FreeParameterExpression, float]): Second angle in radians. + control (Optional[QubitSetInput]): Control qubit(s). Default None. + control_state (Optional[BasisStateInput]): Quantum state on which to control the + operation. Must be a binary sequence of same length as number of qubits in + `control`. Will be ignored if `control` is not present. May be represented as a + string, list, or int. For example "0101", [0, 1, 0, 1], 5 all represent + controlling on qubits 0 and 2 being in the \\|0⟩ state and qubits 1 and 3 being + in the \\|1⟩ state. Default "1" * len(control). + power (float): Integer or fractional power to raise the gate to. Negative + powers will be split into an inverse, accompanied by the positive power. + Default 1. + + Returns: + Iterable[Instruction]: PhaseRx instruction. + + Examples: + >>> circ = Circuit().prx(0, 0.15, 0.25) + """ + return [ + Instruction( + PRx(angle_1, angle_2), + target=qubit, + control=control, + control_state=control_state, + power=power, + ) + for qubit in QubitSet(target) + ] + + +Gate.register_gate(PRx) + + class GPi2(AngledGate): r"""IonQ GPi2 gate. diff --git a/src/braket/circuits/translations.py b/src/braket/circuits/translations.py index 9537460ef..78bb7eed0 100644 --- a/src/braket/circuits/translations.py +++ b/src/braket/circuits/translations.py @@ -67,6 +67,7 @@ "cswap": braket_gates.CSwap, "gpi": braket_gates.GPi, "gpi2": braket_gates.GPi2, + "prx": braket_gates.PRx, "ms": braket_gates.MS, "unitary": braket_gates.Unitary, } diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index b29f3b995..c0c58ad35 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -2536,6 +2536,7 @@ def test_to_unitary_with_global_phase(): (Circuit().cphaseshift00(0, 1, 0.15), gates.CPhaseShift00(0.15).to_matrix()), (Circuit().cphaseshift01(0, 1, 0.15), gates.CPhaseShift01(0.15).to_matrix()), (Circuit().cphaseshift10(0, 1, 0.15), gates.CPhaseShift10(0.15).to_matrix()), + (Circuit().prx(0, 1, 0.15), gates.PRx(1, 0.15).to_matrix()), (Circuit().cy(0, 1), gates.CY().to_matrix()), (Circuit().cz(0, 1), gates.CZ().to_matrix()), (Circuit().xx(0, 1, 0.15), gates.XX(0.15).to_matrix()), diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 1b6ac56c7..3a7e621df 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -39,6 +39,10 @@ class NoTarget: pass +class DoubleAngle: + pass + + class TripleAngle: pass @@ -103,6 +107,7 @@ class SingleNegControlModifier: (Gate.ZZ, "zz", ir.ZZ, [DoubleTarget, Angle], {}), (Gate.GPi, "gpi", None, [SingleTarget, Angle], {}), (Gate.GPi2, "gpi2", None, [SingleTarget, Angle], {}), + (Gate.PRx, "prx", None, [SingleTarget, DoubleAngle], {}), (Gate.MS, "ms", None, [DoubleTarget, TripleAngle], {}), ( Gate.Unitary, @@ -145,9 +150,11 @@ class SingleNegControlModifier: Gate.CPhaseShift10, Gate.GPi, Gate.GPi2, + Gate.PRx, Gate.MS, ] + invalid_unitary_matrices = [ (np.array([[1]])), (np.array([1])), @@ -179,6 +186,10 @@ def angle_valid_input(**kwargs): return {"angle": 0.123} +def double_angle_valid_input(**kwargs): + return {"angle_1": 0.123, "angle_2": 3.567} + + def triple_angle_valid_input(**kwargs): return {"angle_1": 0.123, "angle_2": 4.567, "angle_3": 8.910} @@ -217,6 +228,7 @@ def two_dimensional_matrix_valid_input(**kwargs): "SingleTarget": single_target_valid_input, "DoubleTarget": double_target_valid_ir_input, "Angle": angle_valid_input, + "DoubleAngle": double_angle_valid_input, "TripleAngle": triple_angle_valid_input, "SingleControl": single_control_valid_input, "SingleNegControlModifier": single_neg_control_valid_input, @@ -273,7 +285,7 @@ def create_valid_target_input(irsubclasses): control_state = list(single_neg_control_valid_input()["control_state"]) elif subclass == DoubleControl: qubit_set = list(double_control_valid_ir_input().values()) + qubit_set - elif subclass in (Angle, TwoDimensionalMatrix, TripleAngle): + elif subclass in (Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): pass else: raise ValueError("Invalid subclass") @@ -287,6 +299,8 @@ def create_valid_gate_class_input(irsubclasses, **kwargs): input = {} if Angle in irsubclasses: input.update(angle_valid_input()) + if DoubleAngle in irsubclasses: + input.update(double_angle_valid_input()) if TripleAngle in irsubclasses: input.update(triple_angle_valid_input()) if TwoDimensionalMatrix in irsubclasses: @@ -313,7 +327,7 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif subclass in (NoTarget, Angle, TwoDimensionalMatrix, TripleAngle): + elif subclass in (NoTarget, Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): pass else: raise ValueError("Invalid subclass") @@ -847,6 +861,18 @@ def test_ir_gate_level(testclass, subroutine_name, irclass, irsubclasses, kwargs OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), "gpi2(0.17) $4;", ), + ( + Gate.PRx(angle_1=0.17, angle_2=3.45), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.VIRTUAL), + "prx(0.17, 3.45) q[4];", + ), + ( + Gate.PRx(angle_1=0.17, angle_2=3.45), + [4], + OpenQASMSerializationProperties(qubit_reference_type=QubitReferenceType.PHYSICAL), + "prx(0.17, 3.45) $4;", + ), ( Gate.MS(angle_1=0.17, angle_2=3.45), [4, 5], @@ -902,6 +928,8 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar subroutine_input = {"target": multi_targets} if Angle in irsubclasses: subroutine_input.update(angle_valid_input()) + if DoubleAngle in irsubclasses: + subroutine_input.update(double_angle_valid_input()) if TripleAngle in irsubclasses: subroutine_input.update(triple_angle_valid_input()) assert subroutine(**subroutine_input) == Circuit(instruction_list) @@ -1012,8 +1040,13 @@ def test_large_unitary(): @pytest.mark.parametrize("gate", parameterizable_gates) def test_bind_values(gate): + double_angled = gate.__name__ in ["PRx"] triple_angled = gate.__name__ in ("MS", "U") - num_params = 3 if triple_angled else 1 + num_params = 1 + if triple_angled: + num_params = 3 + elif double_angled: + num_params = 2 thetas = [FreeParameter(f"theta_{i}") for i in range(num_params)] mapping = {f"theta_{i}": i for i in range(num_params)} param_gate = gate(*thetas) @@ -1024,6 +1057,9 @@ def test_bind_values(gate): if triple_angled: for angle in new_gate.angle_1, new_gate.angle_2, new_gate.angle_3: assert isinstance(angle, float) + elif double_angled: + for angle in new_gate.angle_1, new_gate.angle_2: + assert isinstance(angle, float) else: assert isinstance(new_gate.angle, float) From b4920314817b5c8b72f5d4e5e616cebf65484644 Mon Sep 17 00:00:00 2001 From: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:36:54 -0400 Subject: [PATCH 86/98] Feature: Code refactoring with Sourcery (#954) * fix: update to python 39 syntax * fix: sourcery corrections fix: line too long error * fix: tests * fix: tests * fix: tests * Update src/braket/circuits/circuit.py Co-authored-by: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> * fix: update readme, delete .yaml --------- Co-authored-by: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> --- .coveragerc | 2 +- .github/dependabot.yml | 1 - .readthedocs.yml | 2 +- CONTRIBUTING.md | 4 +- README.md | 17 ++--- doc/conf.py | 4 +- doc/examples-adv-circuits-algorithms.rst | 40 +++++------ doc/examples-braket-features.rst | 14 ++-- doc/examples-getting-started.rst | 39 +++++----- doc/examples-hybrid-quantum.rst | 10 +-- doc/examples-ml-pennylane.rst | 30 ++++---- doc/examples.rst | 4 +- doc/getting-started.rst | 5 +- doc/index.rst | 7 +- setup.cfg | 4 +- setup.py | 2 +- src/braket/ahs/atom_arrangement.py | 2 +- src/braket/ahs/discretization_types.py | 2 - src/braket/ahs/local_detuning.py | 2 +- src/braket/aws/aws_device.py | 71 +++++++++---------- src/braket/aws/aws_quantum_job.py | 34 ++++----- src/braket/aws/aws_quantum_task.py | 55 ++++++-------- src/braket/aws/aws_quantum_task_batch.py | 9 +-- src/braket/aws/aws_session.py | 27 ++++--- src/braket/circuits/circuit.py | 34 ++++----- src/braket/circuits/gates.py | 11 ++- src/braket/circuits/moments.py | 6 +- src/braket/circuits/noise.py | 16 ++--- src/braket/circuits/noise_helpers.py | 4 +- .../circuit_instruction_criteria.py | 4 +- src/braket/circuits/noise_model/criteria.py | 8 +-- .../noise_model/criteria_input_parsing.py | 4 +- .../circuits/noise_model/noise_model.py | 5 +- .../noise_model/observable_criteria.py | 4 +- .../qubit_initialization_criteria.py | 4 +- src/braket/circuits/noises.py | 18 ++--- src/braket/circuits/observables.py | 21 +++--- src/braket/circuits/quantum_operator.py | 10 +-- .../circuits/quantum_operator_helpers.py | 2 +- src/braket/circuits/result_types.py | 12 +--- .../ascii_circuit_diagram.py | 14 ++-- .../text_circuit_diagram.py | 2 +- .../text_circuit_diagram_utils.py | 9 +-- .../unicode_circuit_diagram.py | 11 +-- src/braket/devices/device.py | 6 +- src/braket/devices/local_simulator.py | 15 ++-- src/braket/ipython_utils.py | 7 +- src/braket/jobs/environment_variables.py | 4 +- src/braket/jobs/hybrid_job.py | 8 +-- src/braket/jobs/local/local_job.py | 15 ++-- src/braket/jobs/local/local_job_container.py | 4 +- .../jobs/local/local_job_container_setup.py | 8 ++- src/braket/jobs/logs.py | 8 +-- .../cwl_insights_metrics_fetcher.py | 3 +- .../jobs/metrics_data/cwl_metrics_fetcher.py | 18 ++--- src/braket/jobs/metrics_data/exceptions.py | 2 - .../jobs/metrics_data/log_metrics_parser.py | 3 +- src/braket/jobs/quantum_job_creation.py | 54 +++++++------- src/braket/parametric/free_parameter.py | 4 +- src/braket/pulse/ast/approximation_parser.py | 22 +++--- src/braket/pulse/ast/free_parameters.py | 10 +-- src/braket/pulse/ast/qasm_transformer.py | 33 +++++---- src/braket/pulse/pulse_sequence.py | 63 ++++++++-------- src/braket/pulse/waveforms.py | 7 +- .../quantum_information/pauli_string.py | 2 +- src/braket/registers/qubit.py | 5 +- ...iltonian_simulation_quantum_task_result.py | 2 +- .../tasks/gate_model_quantum_task_result.py | 25 +++---- src/braket/timings/time_series.py | 2 +- .../gate_model_device_testing_utils.py | 62 ++++++++-------- test/integ_tests/job_test_script.py | 2 +- .../test_create_local_quantum_job.py | 2 +- test/integ_tests/test_create_quantum_job.py | 19 +++-- test/integ_tests/test_device_creation.py | 12 ++-- .../braket/ahs/test_atom_arrangement.py | 4 +- .../braket/aws/common_test_utils.py | 6 +- test/unit_tests/braket/aws/test_aws_device.py | 30 ++++---- .../braket/aws/test_aws_quantum_job.py | 4 +- .../braket/aws/test_aws_quantum_task.py | 18 ++--- .../unit_tests/braket/aws/test_aws_session.py | 8 +-- .../braket/circuits/test_angled_gate.py | 2 +- .../braket/circuits/test_circuit.py | 35 +++------ test/unit_tests/braket/circuits/test_gates.py | 36 +++++----- .../braket/circuits/test_instruction.py | 13 ++-- .../braket/circuits/test_moments.py | 2 +- .../unit_tests/braket/circuits/test_noises.py | 38 +++++----- .../braket/circuits/test_observable.py | 2 +- .../braket/circuits/test_observables.py | 14 ++-- .../braket/circuits/test_quantum_operator.py | 2 +- .../braket/devices/test_local_simulator.py | 10 +-- .../metrics_data/test_cwl_metrics_fetcher.py | 4 +- .../braket/jobs/test_data_persistence.py | 4 +- .../unit_tests/braket/jobs/test_hybrid_job.py | 2 +- .../braket/jobs/test_quantum_job_creation.py | 24 +++---- .../pulse/ast/test_approximation_parser.py | 26 +++---- .../unit_tests/braket/pulse/test_waveforms.py | 10 +-- .../unit_tests/braket/registers/test_qubit.py | 2 +- .../braket/registers/test_qubit_set.py | 8 +-- .../test_annealing_quantum_task_result.py | 2 +- .../test_gate_model_quantum_task_result.py | 2 +- .../braket/tasks/test_local_quantum_task.py | 2 +- .../braket/timings/test_time_series.py | 8 +-- 102 files changed, 595 insertions(+), 736 deletions(-) diff --git a/.coveragerc b/.coveragerc index 933315e86..fe9662c0a 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,7 +3,7 @@ parallel = True branch = True source = src -omit = +omit = **/braket/ir/* **/braket/device_schema/* **/braket/schema_common/* diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ed79a0d63..04595aed1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,4 +10,3 @@ updates: interval: "weekly" commit-message: prefix: infra - diff --git a/.readthedocs.yml b/.readthedocs.yml index e824a6afc..b6ca23199 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -12,7 +12,7 @@ sphinx: # Optionally build your docs in additional formats such as PDF formats: - pdf - + # setting up build.os and the python version build: os: ubuntu-22.04 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7362a17b7..0df22f51e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -218,9 +218,9 @@ You can then find the generated HTML files in `build/documentation/html`. Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/amazon-braket/amazon-braket-sdk-python/labels/help%20wanted) issues is a great place to start. ## Building Integrations -The Amazon Braket SDK supports integrations with popular quantum computing frameworks such as [PennyLane](https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python), [Strawberryfields](https://github.com/amazon-braket/amazon-braket-strawberryfields-plugin-python) and [DWave's Ocean library](https://github.com/amazon-braket/amazon-braket-ocean-plugin-python). These serve as a good reference for a new integration you wish to develop. +The Amazon Braket SDK supports integrations with popular quantum computing frameworks such as [PennyLane](https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python), [Strawberryfields](https://github.com/amazon-braket/amazon-braket-strawberryfields-plugin-python) and [DWave's Ocean library](https://github.com/amazon-braket/amazon-braket-ocean-plugin-python). These serve as a good reference for a new integration you wish to develop. -When developing a new integration with the Amazon Braket SDK, please remember to update the [user agent header](https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.3) to include version information for your integration. An example can be found [here](https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python/commit/ccee35604afc2b04d83ee9103eccb2821a4256cb). +When developing a new integration with the Amazon Braket SDK, please remember to update the [user agent header](https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.3) to include version information for your integration. An example can be found [here](https://github.com/amazon-braket/amazon-braket-pennylane-plugin-python/commit/ccee35604afc2b04d83ee9103eccb2821a4256cb). ## Code of Conduct diff --git a/README.md b/README.md index 1c79e8034..b03bd1ef4 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,8 @@ Many quantum algorithms need to run multiple independent circuits, and submittin ```python circuits = [bell for _ in range(5)] batch = device.run_batch(circuits, shots=100) -print(batch.results()[0].measurement_counts) # The result of the first quantum task in the batch +# The result of the first quantum task in the batch +print(batch.results()[0].measurement_counts) ``` ### Running a hybrid job @@ -139,14 +140,14 @@ from braket.aws import AwsDevice device = AwsDevice("arn:aws:braket:::device/qpu/rigetti/Aspen-8") bell = Circuit().h(0).cnot(0, 1) -task = device.run(bell) +task = device.run(bell) print(task.result().measurement_counts) ``` When you execute your task, Amazon Braket polls for a result. By default, Braket polls for 5 days; however, it is possible to change this by modifying the `poll_timeout_seconds` parameter in `AwsDevice.run`, as in the example below. Keep in mind that if your polling timeout is too short, results may not be returned within the polling time, such as when a QPU is unavailable, and a local timeout error is returned. You can always restart the polling by using `task.result()`. ```python -task = device.run(bell, poll_timeout_seconds=86400) # 1 day +task = device.run(bell, poll_timeout_seconds=86400) # 1 day print(task.result().measurement_counts) ``` @@ -232,15 +233,15 @@ tox -e integ-tests -- your-arguments ### Issues and Bug Reports -If you encounter bugs or face issues while using the SDK, please let us know by posting -the issue on our [Github issue tracker](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/). +If you encounter bugs or face issues while using the SDK, please let us know by posting +the issue on our [Github issue tracker](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/). For other issues or general questions, please ask on the [Quantum Computing Stack Exchange](https://quantumcomputing.stackexchange.com/questions/ask?Tags=amazon-braket). ### Feedback and Feature Requests -If you have feedback or features that you would like to see on Amazon Braket, we would love to hear from you! -[Github issues](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/) is our preferred mechanism for collecting feedback and feature requests, allowing other users -to engage in the conversation, and +1 issues to help drive priority. +If you have feedback or features that you would like to see on Amazon Braket, we would love to hear from you! +[Github issues](https://github.com/amazon-braket/amazon-braket-sdk-python/issues/) is our preferred mechanism for collecting feedback and feature requests, allowing other users +to engage in the conversation, and +1 issues to help drive priority. ### Code contributors diff --git a/doc/conf.py b/doc/conf.py index a2548fc65..8a16ca231 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -7,7 +7,7 @@ project = "amazon-braket-sdk" version = version(project) release = version -copyright = "{}, Amazon.com".format(datetime.datetime.now().year) +copyright = f"{datetime.datetime.now().year}, Amazon.com" extensions = [ "sphinxcontrib.apidoc", @@ -26,7 +26,7 @@ default_role = "py:obj" html_theme = "sphinx_rtd_theme" -htmlhelp_basename = "{}doc".format(project) +htmlhelp_basename = f"{project}doc" language = "en" diff --git a/doc/examples-adv-circuits-algorithms.rst b/doc/examples-adv-circuits-algorithms.rst index 4a16c9cd6..23de8a047 100644 --- a/doc/examples-adv-circuits-algorithms.rst +++ b/doc/examples-adv-circuits-algorithms.rst @@ -6,26 +6,26 @@ Learn more about working with advanced circuits and algorithms. .. toctree:: :maxdepth: 2 - + ********************************************************************************************************************************************************** `Grover's search algorithm `_ ********************************************************************************************************************************************************** -This tutorial provides a step-by-step walkthrough of Grover's quantum algorithm. -You learn how to build the corresponding quantum circuit with simple modular building -blocks using the Amazon Braket SDK. You will learn how to build custom -gates that are not part of the basic gate set provided by the SDK. A custom gate can used +This tutorial provides a step-by-step walkthrough of Grover's quantum algorithm. +You learn how to build the corresponding quantum circuit with simple modular building +blocks using the Amazon Braket SDK. You will learn how to build custom +gates that are not part of the basic gate set provided by the SDK. A custom gate can used as a core quantum gate by registering it as a subroutine. ****************************************************************************************************************************************************************************************************************** `Quantum amplitude amplification `_ ****************************************************************************************************************************************************************************************************************** -This tutorial provides a detailed discussion and implementation of the Quantum Amplitude Amplification (QAA) -algorithm using the Amazon Braket SDK. QAA is a routine in quantum computing which generalizes the idea behind -Grover's famous search algorithm, with applications across many quantum algorithms. QAA uses an iterative -approach to systematically increase the probability of finding one or multiple -target states in a given search space. In a quantum computer, QAA can be used to obtain a +This tutorial provides a detailed discussion and implementation of the Quantum Amplitude Amplification (QAA) +algorithm using the Amazon Braket SDK. QAA is a routine in quantum computing which generalizes the idea behind +Grover's famous search algorithm, with applications across many quantum algorithms. QAA uses an iterative +approach to systematically increase the probability of finding one or multiple +target states in a given search space. In a quantum computer, QAA can be used to obtain a quadratic speedup over several classical algorithms. @@ -33,18 +33,18 @@ quadratic speedup over several classical algorithms. `Quantum Fourier transform `_ ************************************************************************************************************************************************************************************************ -This tutorial provides a detailed implementation of the Quantum Fourier Transform (QFT) and -its inverse using Amazon Braket's SDK. The QFT is an important subroutine to many quantum algorithms, -most famously Shor's algorithm for factoring and the quantum phase estimation (QPE) algorithm -for estimating the eigenvalues of a unitary operator. +This tutorial provides a detailed implementation of the Quantum Fourier Transform (QFT) and +its inverse using Amazon Braket's SDK. The QFT is an important subroutine to many quantum algorithms, +most famously Shor's algorithm for factoring and the quantum phase estimation (QPE) algorithm +for estimating the eigenvalues of a unitary operator. ********************************************************************************************************************************************************************************************* `Quantum phase estimation `_ ********************************************************************************************************************************************************************************************* -This tutorial provides a detailed implementation of the Quantum Phase Estimation (QPE) -algorithm using the Amazon Braket SDK. The QPE algorithm is designed to estimate the -eigenvalues of a unitary operator. Eigenvalue problems can be found across many -disciplines and application areas, including principal component analysis (PCA) -as used in machine learning and the solution of differential equations in mathematics, physics, -engineering and chemistry. +This tutorial provides a detailed implementation of the Quantum Phase Estimation (QPE) +algorithm using the Amazon Braket SDK. The QPE algorithm is designed to estimate the +eigenvalues of a unitary operator. Eigenvalue problems can be found across many +disciplines and application areas, including principal component analysis (PCA) +as used in machine learning and the solution of differential equations in mathematics, physics, +engineering and chemistry. diff --git a/doc/examples-braket-features.rst b/doc/examples-braket-features.rst index 004e2bfe3..25c088ab1 100644 --- a/doc/examples-braket-features.rst +++ b/doc/examples-braket-features.rst @@ -11,30 +11,30 @@ Learn more about the indivudal features of Amazon Braket. `Getting notifications when a quantum task completes `_ ***************************************************************************************************************************************************************************************************************************************************************** -This tutorial illustrates how Amazon Braket integrates with Amazon EventBridge for -event-based processing. In the tutorial, you will learn how to configure Amazon Braket -and Amazon Eventbridge to receive text notification about quantum task completions on your phone. +This tutorial illustrates how Amazon Braket integrates with Amazon EventBridge for +event-based processing. In the tutorial, you will learn how to configure Amazon Braket +and Amazon Eventbridge to receive text notification about quantum task completions on your phone. *********************************************************************************************************************************************************************** `Allocating Qubits on QPU Devices `_ *********************************************************************************************************************************************************************** -This tutorial explains how you can use the Amazon Braket SDK to allocate the qubit +This tutorial explains how you can use the Amazon Braket SDK to allocate the qubit selection for your circuits manually, when running on QPUs. *************************************************************************************************************************************************************************************************** `Getting Devices and Checking Device Properties `_ *************************************************************************************************************************************************************************************************** -This example shows how to interact with the Amazon Braket GetDevice API to -retrieve Amazon Braket devices (such as simulators and QPUs) programmatically, +This example shows how to interact with the Amazon Braket GetDevice API to +retrieve Amazon Braket devices (such as simulators and QPUs) programmatically, and how to gain access to their properties. *********************************************************************************************************************************************************************************** `Using the tensor network simulator TN1 `_ *********************************************************************************************************************************************************************************** -This notebook introduces the Amazon Braket managed tensor network simulator, TN1. +This notebook introduces the Amazon Braket managed tensor network simulator, TN1. You will learn about how TN1 works, how to use it, and which problems are best suited to run on TN1. diff --git a/doc/examples-getting-started.rst b/doc/examples-getting-started.rst index 8c9eb90f5..64c6939af 100644 --- a/doc/examples-getting-started.rst +++ b/doc/examples-getting-started.rst @@ -6,7 +6,7 @@ Get started on Amazon Braket with some introductory examples. .. toctree:: :maxdepth: 2 - + ********************************************************************************************************************************************************* `Getting started `_ ********************************************************************************************************************************************************* @@ -17,11 +17,11 @@ A hello-world tutorial that shows you how to build a simple circuit and run it o `Running quantum circuits on simulators `_ ****************************************************************************************************************************************************************************************************************************** -This tutorial prepares a paradigmatic example for a multi-qubit entangled state, -the so-called GHZ state (named after the three physicists Greenberger, Horne, and Zeilinger). -The GHZ state is extremely non-classical, and therefore very sensitive to decoherence. -It is often used as a performance benchmark for today's hardware. In many quantum information -protocols it is used as a resource for quantum error correction, quantum communication, +This tutorial prepares a paradigmatic example for a multi-qubit entangled state, +the so-called GHZ state (named after the three physicists Greenberger, Horne, and Zeilinger). +The GHZ state is extremely non-classical, and therefore very sensitive to decoherence. +It is often used as a performance benchmark for today's hardware. In many quantum information +protocols it is used as a resource for quantum error correction, quantum communication, and quantum metrology. **Note:** When a circuit is ran using a simulator, customers are required to use contiguous qubits/indices. @@ -30,30 +30,29 @@ and quantum metrology. `Running quantum circuits on QPU devices `_ ********************************************************************************************************************************************************************************************************************************* -This tutorial prepares a maximally-entangled Bell state between two qubits, -for classical simulators and for QPUs. For classical devices, we can run the circuit on a -local simulator or a cloud-based managed simulator. For the quantum devices, -we run the circuit on the superconducting machine from Rigetti, and on the ion-trap -machine provided by IonQ. +This tutorial prepares a maximally-entangled Bell state between two qubits, +for classical simulators and for QPUs. For classical devices, we can run the circuit on a +local simulator or a cloud-based managed simulator. For the quantum devices, +we run the circuit on the superconducting machine from Rigetti, and on the ion-trap +machine provided by IonQ. ****************************************************************************************************************************************************************************************************************************************************** `Deep Dive into the anatomy of quantum circuits `_ ****************************************************************************************************************************************************************************************************************************************************** -This tutorial discusses in detail the anatomy of quantum circuits in the Amazon -Braket SDK. You will learn how to build (parameterized) circuits and display them +This tutorial discusses in detail the anatomy of quantum circuits in the Amazon +Braket SDK. You will learn how to build (parameterized) circuits and display them graphically, and how to append circuits to each other. Next, learn -more about circuit depth and circuit size. Finally you will learn how to execute -the circuit on a device of our choice (defining a quantum task) and how to track, log, +more about circuit depth and circuit size. Finally you will learn how to execute +the circuit on a device of our choice (defining a quantum task) and how to track, log, recover, or cancel a quantum task efficiently. *************************************************************************************************************************************************************** `Superdense coding `_ *************************************************************************************************************************************************************** -This tutorial constructs an implementation of the superdense coding protocol using -the Amazon Braket SDK. Superdense coding is a method of transmitting two classical -bits by sending only one qubit. Starting with a pair of entanged qubits, the sender -(aka Alice) applies a certain quantum gate to their qubit and sends the result +This tutorial constructs an implementation of the superdense coding protocol using +the Amazon Braket SDK. Superdense coding is a method of transmitting two classical +bits by sending only one qubit. Starting with a pair of entanged qubits, the sender +(aka Alice) applies a certain quantum gate to their qubit and sends the result to the receiver (aka Bob), who is then able to decode the full two-bit message. - diff --git a/doc/examples-hybrid-quantum.rst b/doc/examples-hybrid-quantum.rst index 9c7f3aca2..9a0a8efba 100644 --- a/doc/examples-hybrid-quantum.rst +++ b/doc/examples-hybrid-quantum.rst @@ -11,19 +11,19 @@ Learn more about hybrid quantum algorithms. `QAOA `_ ************************************************************************************************************************************* -This tutorial shows how to (approximately) solve binary combinatorial optimization problems -using the Quantum Approximate Optimization Algorithm (QAOA). +This tutorial shows how to (approximately) solve binary combinatorial optimization problems +using the Quantum Approximate Optimization Algorithm (QAOA). ************************************************************************************************************************************************************************************ `VQE Transverse Ising `_ ************************************************************************************************************************************************************************************ This tutorial shows how to solve for the ground state of the Transverse Ising Model -using the variational quantum eigenvalue solver (VQE). +using the variational quantum eigenvalue solver (VQE). **************************************************************************************************************************************************************** `VQE Chemistry `_ **************************************************************************************************************************************************************** -This tutorial shows how to implement the Variational Quantum Eigensolver (VQE) algorithm in -Amazon Braket SDK to compute the potential energy surface (PES) for the Hydrogen molecule. +This tutorial shows how to implement the Variational Quantum Eigensolver (VQE) algorithm in +Amazon Braket SDK to compute the potential energy surface (PES) for the Hydrogen molecule. diff --git a/doc/examples-ml-pennylane.rst b/doc/examples-ml-pennylane.rst index 5c7db93aa..1aa57cc4c 100644 --- a/doc/examples-ml-pennylane.rst +++ b/doc/examples-ml-pennylane.rst @@ -11,37 +11,37 @@ Learn more about how to combine PennyLane with Amazon Braket. `Combining PennyLane with Amazon Braket `_ ************************************************************************************************************************************************************************** -This tutorial shows you how to construct circuits and evaluate their gradients in +This tutorial shows you how to construct circuits and evaluate their gradients in PennyLane with execution performed using Amazon Braket. ***************************************************************************************************************************************************************************************************************************************************** `Computing gradients in parallel with PennyLane-Braket `_ ***************************************************************************************************************************************************************************************************************************************************** -Learn how to speed up training of quantum circuits by using parallel execution on -Amazon Braket. Quantum circuit training involving gradients -requires multiple device executions. The Amazon Braket SV1 simulator can be used to overcome this. -The tutorial benchmarks SV1 against a local simulator, showing that SV1 outperforms the -local simulator for both executions and gradient calculations. This illustrates how +Learn how to speed up training of quantum circuits by using parallel execution on +Amazon Braket. Quantum circuit training involving gradients +requires multiple device executions. The Amazon Braket SV1 simulator can be used to overcome this. +The tutorial benchmarks SV1 against a local simulator, showing that SV1 outperforms the +local simulator for both executions and gradient calculations. This illustrates how parallel capabilities can be combined between PennyLane and SV1. ****************************************************************************************************************************************************************************************** `Graph optimization with QAOA `_ ****************************************************************************************************************************************************************************************** -In this tutorial, you learn how quantum circuit training can be applied to a problem -of practical relevance in graph optimization. It easy it is to train a QAOA circuit in -PennyLane to solve the maximum clique problem on a simple example graph. The tutorial -then extends to a more difficult 20-node graph and uses the parallel capabilities of -the Amazon Braket SV1 simulator to speed up gradient calculations and hence train the quantum circuit faster, +In this tutorial, you learn how quantum circuit training can be applied to a problem +of practical relevance in graph optimization. It easy it is to train a QAOA circuit in +PennyLane to solve the maximum clique problem on a simple example graph. The tutorial +then extends to a more difficult 20-node graph and uses the parallel capabilities of +the Amazon Braket SV1 simulator to speed up gradient calculations and hence train the quantum circuit faster, using around 1-2 minutes per iteration. *************************************************************************************************************************************************************************************************************** `Hydrogen Molecule geometry with VQE `_ *************************************************************************************************************************************************************************************************************** -In this tutorial, you will learn how PennyLane and Amazon Braket can be combined to solve an -important problem in quantum chemistry. The ground state energy of molecular hydrogen is calculated -by optimizing a VQE circuit using the local Braket simulator. This tutorial highlights how -qubit-wise commuting observables can be measured together in PennyLane and Amazon Braket, +In this tutorial, you will learn how PennyLane and Amazon Braket can be combined to solve an +important problem in quantum chemistry. The ground state energy of molecular hydrogen is calculated +by optimizing a VQE circuit using the local Braket simulator. This tutorial highlights how +qubit-wise commuting observables can be measured together in PennyLane and Amazon Braket, making optimization more efficient. diff --git a/doc/examples.rst b/doc/examples.rst index 87c2e1f7a..93aac757b 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -1,7 +1,7 @@ ######## Examples ######## - + There are several examples available in the Amazon Braket repo: https://github.com/amazon-braket/amazon-braket-examples. @@ -14,5 +14,3 @@ https://github.com/amazon-braket/amazon-braket-examples. examples-hybrid-quantum.rst examples-ml-pennylane.rst examples-hybrid-jobs.rst - - diff --git a/doc/getting-started.rst b/doc/getting-started.rst index 205254740..31493b789 100644 --- a/doc/getting-started.rst +++ b/doc/getting-started.rst @@ -16,7 +16,7 @@ at https://docs.aws.amazon.com/braket/index.html. Getting started using an Amazon Braket notebook ************************************************ -You can use the AWS Console to enable Amazon Braket, +You can use the AWS Console to enable Amazon Braket, then create an Amazon Braket notebook instance and run your first circuit with the Amazon Braket Python SDK: @@ -25,7 +25,7 @@ and run your first circuit with the Amazon Braket Python SDK: 3. `Run your first circuit using the Amazon Braket Python SDK `_. When you use an Amazon Braket notebook, the Amazon Braket SDK and plugins are -preloaded. +preloaded. *********************************** Getting started in your environment @@ -37,4 +37,3 @@ after enabling Amazon Braket and configuring the AWS SDK for Python: 1. `Enable Amazon Braket `_. 2. Configure the AWS SDK for Python (Boto3) using the `Quickstart `_. 3. `Run your first circuit using the Amazon Braket Python SDK `_. - diff --git a/doc/index.rst b/doc/index.rst index 8d996f4cc..54d10b54d 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -2,7 +2,7 @@ Amazon Braket Python SDK ######################## -The Amazon Braket Python SDK is an open source library to design and build quantum circuits, +The Amazon Braket Python SDK is an open source library to design and build quantum circuits, submit them to Amazon Braket devices as quantum tasks, and monitor their execution. This documentation provides information about the Amazon Braket Python SDK library. The project @@ -29,7 +29,7 @@ Explore Amazon Braket examples. :maxdepth: 3 examples.rst - + *************** Python SDK APIs @@ -39,6 +39,5 @@ The Amazon Braket Python SDK APIs: .. toctree:: :maxdepth: 2 - - _apidoc/modules + _apidoc/modules diff --git a/setup.cfg b/setup.cfg index d9dbb5b62..d75c4f034 100644 --- a/setup.cfg +++ b/setup.cfg @@ -18,7 +18,7 @@ line_length = 100 multi_line_output = 3 include_trailing_comma = true profile = black - + [flake8] ignore = # not pep8, black adds whitespace before ':' @@ -32,7 +32,7 @@ ignore = RST201,RST203,RST301, max_line_length = 100 max-complexity = 10 -exclude = +exclude = __pycache__ .tox .git diff --git a/setup.py b/setup.py index 12bec6f07..6763c4a55 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ from setuptools import find_namespace_packages, setup -with open("README.md", "r") as fh: +with open("README.md") as fh: long_description = fh.read() with open("src/braket/_sdk/_version.py") as f: diff --git a/src/braket/ahs/atom_arrangement.py b/src/braket/ahs/atom_arrangement.py index 1d47a66b8..24d4fc9aa 100644 --- a/src/braket/ahs/atom_arrangement.py +++ b/src/braket/ahs/atom_arrangement.py @@ -126,4 +126,4 @@ def discretize(self, properties: DiscretizationProperties) -> AtomArrangement: discretized_arrangement.add(new_coordinates, site.site_type) return discretized_arrangement except Exception as e: - raise DiscretizationError(f"Failed to discretize register {e}") + raise DiscretizationError(f"Failed to discretize register {e}") from e diff --git a/src/braket/ahs/discretization_types.py b/src/braket/ahs/discretization_types.py index c7df1fcfc..49efa0d34 100644 --- a/src/braket/ahs/discretization_types.py +++ b/src/braket/ahs/discretization_types.py @@ -18,8 +18,6 @@ class DiscretizationError(Exception): """Raised if the discretization of the numerical values of the AHS program fails.""" - pass - @dataclass class DiscretizationProperties: diff --git a/src/braket/ahs/local_detuning.py b/src/braket/ahs/local_detuning.py index 59732d4ff..00b420210 100644 --- a/src/braket/ahs/local_detuning.py +++ b/src/braket/ahs/local_detuning.py @@ -136,7 +136,7 @@ def stitch( stitch_ts.times() = [0, 0.1, 0.3] stitch_ts.values() = [1, 4, 5] """ - if not (self.magnitude.pattern.series == other.magnitude.pattern.series): + if self.magnitude.pattern.series != other.magnitude.pattern.series: raise ValueError("The LocalDetuning pattern for both fields must be equal.") new_ts = self.magnitude.time_series.stitch(other.magnitude.time_series, boundary) diff --git a/src/braket/aws/aws_device.py b/src/braket/aws/aws_device.py index 390145aec..70fc3c078 100644 --- a/src/braket/aws/aws_device.py +++ b/src/braket/aws/aws_device.py @@ -354,7 +354,7 @@ def _get_regional_device_session(self, session: AwsSession) -> AwsSession: ValueError(f"'{self._arn}' not found") if e.response["Error"]["Code"] == "ResourceNotFoundException" else e - ) + ) from e def _get_non_regional_device_session(self, session: AwsSession) -> AwsSession: current_region = session.region @@ -362,11 +362,10 @@ def _get_non_regional_device_session(self, session: AwsSession) -> AwsSession: self._populate_properties(session) return session except ClientError as e: - if e.response["Error"]["Code"] == "ResourceNotFoundException": - if "qpu" not in self._arn: - raise ValueError(f"Simulator '{self._arn}' not found in '{current_region}'") - else: + if e.response["Error"]["Code"] != "ResourceNotFoundException": raise e + if "qpu" not in self._arn: + raise ValueError(f"Simulator '{self._arn}' not found in '{current_region}'") from e # Search remaining regions for QPU for region in frozenset(AwsDevice.REGIONS) - {current_region}: region_session = AwsSession.copy_session(session, region) @@ -623,7 +622,7 @@ def get_devices( f"order_by '{order_by}' must be in {AwsDevice._GET_DEVICES_ORDER_BY_KEYS}" ) types = frozenset(types or AwsDeviceType) - aws_session = aws_session if aws_session else AwsSession() + aws_session = aws_session or AwsSession() device_map = {} session_region = aws_session.boto_session.region_name search_regions = ( @@ -650,13 +649,11 @@ def get_devices( provider_names=provider_names, ) ] - device_map.update( - { - arn: AwsDevice(arn, session_for_region) - for arn in region_device_arns - if arn not in device_map - } - ) + device_map |= { + arn: AwsDevice(arn, session_for_region) + for arn in region_device_arns + if arn not in device_map + } except ClientError as e: error_code = e.response["Error"]["Code"] warnings.warn( @@ -671,29 +668,29 @@ def get_devices( return devices def _update_pulse_properties(self) -> None: - if hasattr(self.properties, "pulse") and isinstance( + if not hasattr(self.properties, "pulse") or not isinstance( self.properties.pulse, PulseDeviceActionProperties ): - if self._ports is None: - self._ports = {} - port_data = self.properties.pulse.ports - for port_id, port in port_data.items(): - self._ports[port_id] = Port( - port_id=port_id, dt=port.dt, properties=json.loads(port.json()) + return + if self._ports is None: + self._ports = {} + port_data = self.properties.pulse.ports + for port_id, port in port_data.items(): + self._ports[port_id] = Port( + port_id=port_id, dt=port.dt, properties=json.loads(port.json()) + ) + if self._frames is None: + self._frames = {} + if frame_data := self.properties.pulse.frames: + for frame_id, frame in frame_data.items(): + self._frames[frame_id] = Frame( + frame_id=frame_id, + port=self._ports[frame.portId], + frequency=frame.frequency, + phase=frame.phase, + is_predefined=True, + properties=json.loads(frame.json()), ) - if self._frames is None: - self._frames = {} - frame_data = self.properties.pulse.frames - if frame_data: - for frame_id, frame in frame_data.items(): - self._frames[frame_id] = Frame( - frame_id=frame_id, - port=self._ports[frame.portId], - frequency=frame.frequency, - phase=frame.phase, - is_predefined=True, - properties=json.loads(frame.json()), - ) @staticmethod def get_device_region(device_arn: str) -> str: @@ -710,11 +707,11 @@ def get_device_region(device_arn: str) -> str: """ try: return device_arn.split(":")[3] - except IndexError: + except IndexError as e: raise ValueError( f"Device ARN is not a valid format: {device_arn}. For valid Braket ARNs, " "see 'https://docs.aws.amazon.com/braket/latest/developerguide/braket-devices.html'" - ) + ) from e def queue_depth(self) -> QueueDepthInfo: """Task queue depth refers to the total number of quantum tasks currently waiting @@ -788,10 +785,10 @@ def refresh_gate_calibrations(self) -> Optional[GateCalibrations]: json.loads(f.read().decode("utf-8")) ) return GateCalibrations(json_calibration_data) - except urllib.error.URLError: + except urllib.error.URLError as e: raise urllib.error.URLError( f"Unable to reach {self.properties.pulse.nativeGateCalibrationsRef}" - ) + ) from e else: return None diff --git a/src/braket/aws/aws_quantum_job.py b/src/braket/aws/aws_quantum_job.py index 3c6dbe66e..37eb67603 100644 --- a/src/braket/aws/aws_quantum_job.py +++ b/src/braket/aws/aws_quantum_job.py @@ -412,7 +412,7 @@ def logs(self, wait: bool = False, poll_interval_seconds: int = 5) -> None: has_streams, color_wrap, [previous_state, current_state], - self.queue_position().queue_position if not self._quiet else None, + None if self._quiet else self.queue_position().queue_position, ) previous_state = current_state @@ -575,17 +575,16 @@ def _attempt_results_download(self, output_bucket_uri: str, output_s3_path: str) s3_uri=output_bucket_uri, filename=AwsQuantumJob.RESULTS_TAR_FILENAME ) except ClientError as e: - if e.response["Error"]["Code"] == "404": - exception_response = { - "Error": { - "Code": "404", - "Message": f"Error retrieving results, " - f"could not find results at '{output_s3_path}'", - } - } - raise ClientError(exception_response, "HeadObject") from e - else: + if e.response["Error"]["Code"] != "404": raise e + exception_response = { + "Error": { + "Code": "404", + "Message": f"Error retrieving results, " + f"could not find results at '{output_s3_path}'", + } + } + raise ClientError(exception_response, "HeadObject") from e @staticmethod def _extract_tar_file(extract_path: str) -> None: @@ -596,9 +595,7 @@ def __repr__(self) -> str: return f"AwsQuantumJob('arn':'{self.arn}')" def __eq__(self, other: AwsQuantumJob) -> bool: - if isinstance(other, AwsQuantumJob): - return self.arn == other.arn - return False + return self.arn == other.arn if isinstance(other, AwsQuantumJob) else False def __hash__(self) -> int: return hash(self.arn) @@ -632,7 +629,7 @@ def _initialize_regional_device_session( ValueError(f"'{device}' not found.") if e.response["Error"]["Code"] == "ResourceNotFoundException" else e - ) + ) from e @staticmethod def _initialize_non_regional_device_session( @@ -643,12 +640,11 @@ def _initialize_non_regional_device_session( aws_session.get_device(device) return aws_session except ClientError as e: - if e.response["Error"]["Code"] == "ResourceNotFoundException": - if "qpu" not in device: - raise ValueError(f"Simulator '{device}' not found in '{original_region}'") - else: + if e.response["Error"]["Code"] != "ResourceNotFoundException": raise e + if "qpu" not in device: + raise ValueError(f"Simulator '{device}' not found in '{original_region}'") from e for region in frozenset(AwsDevice.REGIONS) - {original_region}: device_session = aws_session.copy_session(region=region) try: diff --git a/src/braket/aws/aws_quantum_task.py b/src/braket/aws/aws_quantum_task.py index c386469b0..17ae29252 100644 --- a/src/braket/aws/aws_quantum_task.py +++ b/src/braket/aws/aws_quantum_task.py @@ -205,8 +205,7 @@ def create( if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} - unbounded_parameters = param_names - set(inputs.keys()) - if unbounded_parameters: + if unbounded_parameters := param_names - set(inputs.keys()): raise ValueError( f"Cannot execute circuit with unbound parameters: {unbounded_parameters}" ) @@ -294,11 +293,9 @@ def id(self) -> str: def _cancel_future(self) -> None: """Cancel the future if it exists. Else, create a cancelled future.""" - if hasattr(self, "_future"): - self._future.cancel() - else: + if not hasattr(self, "_future"): self._future = asyncio.Future() - self._future.cancel() + self._future.cancel() def cancel(self) -> None: """Cancel the quantum task. This cancels the future and the quantum task in Amazon @@ -509,10 +506,10 @@ async def _wait_for_completion( return None def _has_reservation_arn_from_metadata(self, current_metadata: dict[str, Any]) -> bool: - for association in current_metadata.get("associations", []): - if association.get("type") == "RESERVATION_TIME_WINDOW_ARN": - return True - return False + return any( + association.get("type") == "RESERVATION_TIME_WINDOW_ARN" + for association in current_metadata.get("associations", []) + ) def _download_result( self, @@ -544,9 +541,7 @@ def __repr__(self) -> str: return f"AwsQuantumTask('id/taskArn':'{self.id}')" def __eq__(self, other: AwsQuantumTask) -> bool: - if isinstance(other, AwsQuantumTask): - return self.id == other.id - return False + return self.id == other.id if isinstance(other, AwsQuantumTask) else False def __hash__(self) -> int: return hash(self.id) @@ -583,10 +578,10 @@ def _( ) -> AwsQuantumTask: openqasm_program = OpenQASMProgram( source=pulse_sequence.to_ir(), - inputs=inputs if inputs else {}, + inputs=inputs or {}, ) - create_task_kwargs.update({"action": openqasm_program.json()}) + create_task_kwargs["action"] = openqasm_program.json() task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -611,7 +606,7 @@ def _( source=openqasm_program.source, inputs=inputs_copy, ) - create_task_kwargs.update({"action": openqasm_program.json()}) + create_task_kwargs["action"] = openqasm_program.json() if device_parameters: final_device_parameters = ( _circuit_device_params_from_dict( @@ -622,9 +617,7 @@ def _( if isinstance(device_parameters, dict) else device_parameters ) - create_task_kwargs.update( - {"deviceParameters": final_device_parameters.json(exclude_none=True)} - ) + create_task_kwargs["deviceParameters"] = final_device_parameters.json(exclude_none=True) task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -643,7 +636,7 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: - create_task_kwargs.update({"action": blackbird_program.json()}) + create_task_kwargs["action"] = blackbird_program.json() task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -701,12 +694,10 @@ def _( inputs=inputs_copy, ) - create_task_kwargs.update( - { - "action": openqasm_program.json(), - "deviceParameters": final_device_parameters.json(exclude_none=True), - } - ) + create_task_kwargs |= { + "action": openqasm_program.json(), + "deviceParameters": final_device_parameters.json(exclude_none=True), + } task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -730,12 +721,10 @@ def _( **kwargs, ) -> AwsQuantumTask: device_params = _create_annealing_device_params(device_parameters, device_arn) - create_task_kwargs.update( - { - "action": problem.to_ir().json(), - "deviceParameters": device_params.json(exclude_none=True), - } - ) + create_task_kwargs |= { + "action": problem.to_ir().json(), + "deviceParameters": device_params.json(exclude_none=True), + } task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) @@ -754,7 +743,7 @@ def _( *args, **kwargs, ) -> AwsQuantumTask: - create_task_kwargs.update({"action": analog_hamiltonian_simulation.to_ir().json()}) + create_task_kwargs["action"] = analog_hamiltonian_simulation.to_ir().json() task_arn = aws_session.create_quantum_task(**create_task_kwargs) return AwsQuantumTask(task_arn, aws_session, *args, **kwargs) diff --git a/src/braket/aws/aws_quantum_task_batch.py b/src/braket/aws/aws_quantum_task_batch.py index 964fabe7e..300963a6f 100644 --- a/src/braket/aws/aws_quantum_task_batch.py +++ b/src/braket/aws/aws_quantum_task_batch.py @@ -209,8 +209,7 @@ def _tasks_inputs_gatedefs( for task_specification, input_map, _gate_definitions in tasks_inputs_definitions: if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} - unbounded_parameters = param_names - set(input_map.keys()) - if unbounded_parameters: + if unbounded_parameters := param_names - set(input_map.keys()): raise ValueError( f"Cannot execute circuit with unbound parameters: " f"{unbounded_parameters}" @@ -323,9 +322,7 @@ def _create_task( # If the quantum task hits a terminal state before all quantum tasks have been created, # it can be returned immediately - while remaining: - if task.state() in AwsQuantumTask.TERMINAL_STATES: - break + while remaining and task.state() not in AwsQuantumTask.TERMINAL_STATES: time.sleep(poll_interval_seconds) return task @@ -363,7 +360,7 @@ def results( retries = 0 while self._unsuccessful and retries < max_retries: self.retry_unsuccessful_tasks() - retries = retries + 1 + retries += 1 if fail_unsuccessful and self._unsuccessful: raise RuntimeError( diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 6a1bd1ce8..16a021e7d 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -238,7 +238,7 @@ def create_quantum_task(self, **boto3_kwargs) -> str: # Add job token to request, if available. job_token = os.getenv("AMZN_BRAKET_JOB_TOKEN") if job_token: - boto3_kwargs.update({"jobToken": job_token}) + boto3_kwargs["jobToken"] = job_token response = self.braket_client.create_quantum_task(**boto3_kwargs) broadcast_event( _TaskCreationEvent( @@ -402,7 +402,7 @@ def upload_local_data(self, local_prefix: str, s3_prefix: str) -> None: relative_prefix = str(Path(local_prefix).relative_to(base_dir)) else: base_dir = Path() - relative_prefix = str(local_prefix) + relative_prefix = local_prefix for file in itertools.chain( # files that match the prefix base_dir.glob(f"{relative_prefix}*"), @@ -579,7 +579,12 @@ def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str) error_code = e.response["Error"]["Code"] message = e.response["Error"]["Message"] - if error_code == "BucketAlreadyOwnedByYou": + if ( + error_code == "BucketAlreadyOwnedByYou" + or error_code != "BucketAlreadyExists" + and error_code == "OperationAborted" + and "conflicting conditional operation" in message + ): pass elif error_code == "BucketAlreadyExists": raise ValueError( @@ -587,12 +592,6 @@ def _create_s3_bucket_if_it_does_not_exist(self, bucket_name: str, region: str) f"for another account. Please supply alternative " f"bucket name via AwsSession constructor `AwsSession()`." ) from None - elif ( - error_code == "OperationAborted" and "conflicting conditional operation" in message - ): - # If this bucket is already being concurrently created, we don't need to create - # it again. - pass else: raise @@ -691,8 +690,8 @@ def parse_s3_uri(s3_uri: str) -> tuple[str, str]: raise AssertionError bucket, key = s3_uri_match.groups() return bucket, key - except (AssertionError, ValueError): - raise ValueError(f"Not a valid S3 uri: {s3_uri}") + except (AssertionError, ValueError) as e: + raise ValueError(f"Not a valid S3 uri: {s3_uri}") from e @staticmethod def construct_s3_uri(bucket: str, *dirs: str) -> str: @@ -740,10 +739,10 @@ def describe_log_streams( } if limit: - log_stream_args.update({"limit": limit}) + log_stream_args["limit"] = limit if next_token: - log_stream_args.update({"nextToken": next_token}) + log_stream_args["nextToken"] = next_token return self.logs_client.describe_log_streams(**log_stream_args) @@ -777,7 +776,7 @@ def get_log_events( } if next_token: - log_events_args.update({"nextToken": next_token}) + log_events_args["nextToken"] = next_token return self.logs_client.get_log_events(**log_events_args) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index bf0ee07d8..94d53248c 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -164,11 +164,9 @@ def depth(self) -> int: def global_phase(self) -> float: """float: Get the global phase of the circuit.""" return sum( - [ - instr.operator.angle - for moment, instr in self._moments.items() - if moment.moment_type == MomentType.GLOBAL_PHASE - ] + instr.operator.angle + for moment, instr in self._moments.items() + if moment.moment_type == MomentType.GLOBAL_PHASE ) @property @@ -196,8 +194,7 @@ def basis_rotation_instructions(self) -> list[Instruction]: # Note that basis_rotation_instructions can change each time a new instruction # is added to the circuit because `self._moments.qubits` would change basis_rotation_instructions = [] - all_qubit_observable = self._qubit_observable_mapping.get(Circuit._ALL_QUBITS) - if all_qubit_observable: + if all_qubit_observable := self._qubit_observable_mapping.get(Circuit._ALL_QUBITS): for target in self.qubits: basis_rotation_instructions += Circuit._observable_to_instruction( all_qubit_observable, target @@ -880,7 +877,7 @@ def apply_gate_noise( # check target_qubits target_qubits = check_noise_target_qubits(self, target_qubits) - if not all(qubit in self.qubits for qubit in target_qubits): + if any(qubit not in self.qubits for qubit in target_qubits): raise IndexError("target_qubits must be within the range of the current circuit.") # Check if there is a measure instruction on the circuit @@ -1007,9 +1004,7 @@ def _validate_parameters(self, parameter_values: dict[str, Number]) -> None: ValueError: If there are no parameters that match the key for the arg param_values. """ - parameter_strings = set() - for parameter in self.parameters: - parameter_strings.add(str(parameter)) + parameter_strings = {str(parameter) for parameter in self.parameters} for param in parameter_values: if param not in parameter_strings: raise ValueError(f"No parameter in the circuit named: {param}") @@ -1116,7 +1111,7 @@ def apply_readout_noise( target_qubits = [target_qubits] if not all(isinstance(q, int) for q in target_qubits): raise TypeError("target_qubits must be integer(s)") - if not all(q >= 0 for q in target_qubits): + if any(q < 0 for q in target_qubits): raise ValueError("target_qubits must contain only non-negative integers.") target_qubits = QubitSet(target_qubits) @@ -1349,8 +1344,7 @@ def _create_openqasm_header( ) -> list[str]: ir_instructions = ["OPENQASM 3.0;"] frame_wf_declarations = self._generate_frame_wf_defcal_declarations(gate_definitions) - for parameter in self.parameters: - ir_instructions.append(f"input float {parameter};") + ir_instructions.extend(f"input float {parameter};" for parameter in self.parameters) if not self.result_types: bit_count = ( len(self._measure_targets) @@ -1378,7 +1372,7 @@ def _validate_gate_calibrations_uniqueness( frames: dict[str, Frame], waveforms: dict[str, Waveform], ) -> None: - for _key, calibration in gate_definitions.items(): + for calibration in gate_definitions.values(): for frame in calibration._frames.values(): _validate_uniqueness(frames, frame) frames[frame.id] = frame @@ -1466,7 +1460,7 @@ def _get_frames_waveforms_from_instrs( fixed_argument_calibrations = self._add_fixed_argument_calibrations( gate_definitions, instruction ) - gate_definitions.update(fixed_argument_calibrations) + gate_definitions |= fixed_argument_calibrations return frames, waveforms def _add_fixed_argument_calibrations( @@ -1509,7 +1503,7 @@ def _add_fixed_argument_calibrations( instruction.operator.parameters ) == len(gate.parameters): free_parameter_number = sum( - [isinstance(p, FreeParameterExpression) for p in gate.parameters] + isinstance(p, FreeParameterExpression) for p in gate.parameters ) if free_parameter_number == 0: continue @@ -1563,10 +1557,10 @@ def to_unitary(self) -> np.ndarray: [ 0.70710678+0.j, 0. +0.j, -0.70710678+0.j, 0. +0.j]]) """ - qubits = self.qubits - if not qubits: + if qubits := self.qubits: + return calculate_unitary_big_endian(self.instructions, qubits) + else: return np.zeros(0, dtype=complex) - return calculate_unitary_big_endian(self.instructions, qubits) @property def qubits_frozen(self) -> bool: diff --git a/src/braket/circuits/gates.py b/src/braket/circuits/gates.py index bc4c2f9e9..ee5ea684b 100644 --- a/src/braket/circuits/gates.py +++ b/src/braket/circuits/gates.py @@ -3702,9 +3702,7 @@ def _to_openqasm( return f"#pragma braket unitary({formatted_matrix}) {', '.join(qubits)}" def __eq__(self, other: Unitary): - if isinstance(other, Unitary): - return self.matrix_equivalence(other) - return False + return self.matrix_equivalence(other) if isinstance(other, Unitary) else False def __hash__(self): return hash((self.name, str(self._matrix), self.qubit_count)) @@ -3859,11 +3857,10 @@ def format_complex(number: complex) -> str: str: The formatted string. """ if number.real: - if number.imag: - imag_sign = "+" if number.imag > 0 else "-" - return f"{number.real} {imag_sign} {abs(number.imag)}im" - else: + if not number.imag: return f"{number.real}" + imag_sign = "+" if number.imag > 0 else "-" + return f"{number.real} {imag_sign} {abs(number.imag)}im" elif number.imag: return f"{number.imag}im" else: diff --git a/src/braket/circuits/moments.py b/src/braket/circuits/moments.py index 66ea41913..b2dee4151 100644 --- a/src/braket/circuits/moments.py +++ b/src/braket/circuits/moments.py @@ -238,7 +238,7 @@ def add_noise( time = 0 while MomentsKey(time, qubit_range, input_type, noise_index) in self._moments: - noise_index = noise_index + 1 + noise_index += 1 self._moments[MomentsKey(time, qubit_range, input_type, noise_index)] = instruction self._qubits.update(qubit_range) @@ -341,9 +341,7 @@ def __eq__(self, other: Moments): def __ne__(self, other: Moments): result = self.__eq__(other) - if result is not NotImplemented: - return not result - return NotImplemented + return not result if result is not NotImplemented else NotImplemented def __repr__(self): return self._moments.__repr__() diff --git a/src/braket/circuits/noise.py b/src/braket/circuits/noise.py index 0cad544e2..e5d4fdf8a 100644 --- a/src/braket/circuits/noise.py +++ b/src/braket/circuits/noise.py @@ -137,9 +137,7 @@ def to_matrix(self, *args, **kwargs) -> Iterable[np.ndarray]: raise NotImplementedError("to_matrix has not been implemented yet.") def __eq__(self, other: Noise): - if isinstance(other, Noise): - return self.name == other.name - return False + return self.name == other.name if isinstance(other, Noise) else False def __repr__(self): return f"{self.name}('qubit_count': {self.qubit_count})" @@ -468,9 +466,10 @@ def to_dict(self) -> dict: dict: A dictionary object that represents this object. It can be converted back into this object using the `from_dict()` method. """ - probabilities = {} - for pauli_string, prob in self._probabilities.items(): - probabilities[pauli_string] = _parameter_to_dict(prob) + probabilities = { + pauli_string: _parameter_to_dict(prob) + for pauli_string, prob in self._probabilities.items() + } return { "__class__": self.__class__.__name__, "probabilities": probabilities, @@ -537,9 +536,8 @@ def _get_param_float(param: Union[FreeParameterExpression, float], param_name: s """ if isinstance(param, FreeParameterExpression): return 0 - else: - _validate_param_value(param, param_name) - return float(param) + _validate_param_value(param, param_name) + return float(param) @property def probX(self) -> Union[FreeParameterExpression, float]: diff --git a/src/braket/circuits/noise_helpers.py b/src/braket/circuits/noise_helpers.py index 89f30cac8..a73b7f338 100644 --- a/src/braket/circuits/noise_helpers.py +++ b/src/braket/circuits/noise_helpers.py @@ -36,7 +36,7 @@ def no_noise_applied_warning(noise_applied: bool) -> None: Args: noise_applied (bool): True if the noise has been applied. """ - if noise_applied is False: + if not noise_applied: warnings.warn( "Noise is not applied to any gate, as there is no eligible gate in the circuit" " with the input criteria or there is no multi-qubit gate to apply" @@ -122,7 +122,7 @@ def check_noise_target_qubits( target_qubits = wrap_with_list(target_qubits) if not all(isinstance(q, int) for q in target_qubits): raise TypeError("target_qubits must be integer(s)") - if not all(q >= 0 for q in target_qubits): + if any(q < 0 for q in target_qubits): raise ValueError("target_qubits must contain only non-negative integers.") target_qubits = QubitSet(target_qubits) diff --git a/src/braket/circuits/noise_model/circuit_instruction_criteria.py b/src/braket/circuits/noise_model/circuit_instruction_criteria.py index 1db40aa5f..4dceeb4fb 100644 --- a/src/braket/circuits/noise_model/circuit_instruction_criteria.py +++ b/src/braket/circuits/noise_model/circuit_instruction_criteria.py @@ -53,6 +53,4 @@ def _check_target_in_qubits( if qubits is None: return True target = [int(item) for item in target] - if len(target) == 1: - return target[0] in qubits - return tuple(target) in qubits + return target[0] in qubits if len(target) == 1 else tuple(target) in qubits diff --git a/src/braket/circuits/noise_model/criteria.py b/src/braket/circuits/noise_model/criteria.py index 63625491f..889211342 100644 --- a/src/braket/circuits/noise_model/criteria.py +++ b/src/braket/circuits/noise_model/criteria.py @@ -73,10 +73,10 @@ def __eq__(self, other: Criteria): return NotImplemented if self.applicable_key_types() != other.applicable_key_types(): return False - for key_type in self.applicable_key_types(): - if self.get_keys(key_type) != other.get_keys(key_type): - return False - return True + return all( + self.get_keys(key_type) == other.get_keys(key_type) + for key_type in self.applicable_key_types() + ) @abstractmethod def to_dict(self) -> dict: diff --git a/src/braket/circuits/noise_model/criteria_input_parsing.py b/src/braket/circuits/noise_model/criteria_input_parsing.py index bc86e53bf..456867ce2 100644 --- a/src/braket/circuits/noise_model/criteria_input_parsing.py +++ b/src/braket/circuits/noise_model/criteria_input_parsing.py @@ -84,6 +84,4 @@ def parse_qubit_input( if qubit_count == 1: return {item[0] for item in qubits} return {tuple(item) for item in qubits} - if qubit_count > 1: - return {tuple(qubits)} - return set(qubits) + return {tuple(qubits)} if qubit_count > 1 else set(qubits) diff --git a/src/braket/circuits/noise_model/noise_model.py b/src/braket/circuits/noise_model/noise_model.py index 4ea566131..e8a603075 100644 --- a/src/braket/circuits/noise_model/noise_model.py +++ b/src/braket/circuits/noise_model/noise_model.py @@ -343,10 +343,9 @@ def _items_to_string( list[str]: A list of string representations of the passed instructions. """ results = [] - if len(instructions) > 0: + if instructions: results.append(instructions_title) - for item in instructions: - results.append(f" {item}") + results.extend(f" {item}" for item in instructions) return results @classmethod diff --git a/src/braket/circuits/noise_model/observable_criteria.py b/src/braket/circuits/noise_model/observable_criteria.py index 1a2126502..5cb510f2e 100644 --- a/src/braket/circuits/noise_model/observable_criteria.py +++ b/src/braket/circuits/noise_model/observable_criteria.py @@ -132,9 +132,7 @@ def result_type_matches(self, result_type: ResultType) -> bool: if self._qubits is None: return True target = list(result_type.target) - if not target: - return True - return target[0] in self._qubits + return target[0] in self._qubits if target else True @classmethod def from_dict(cls, criteria: dict) -> Criteria: diff --git a/src/braket/circuits/noise_model/qubit_initialization_criteria.py b/src/braket/circuits/noise_model/qubit_initialization_criteria.py index e1790fd21..26594ca60 100644 --- a/src/braket/circuits/noise_model/qubit_initialization_criteria.py +++ b/src/braket/circuits/noise_model/qubit_initialization_criteria.py @@ -59,9 +59,7 @@ def get_keys(self, key_type: CriteriaKey) -> Union[CriteriaKeyResult, set[Any]]: All other keys will return an empty set. """ if key_type == CriteriaKey.QUBIT: - if self._qubits is None: - return CriteriaKeyResult.ALL - return set(self._qubits) + return CriteriaKeyResult.ALL if self._qubits is None else set(self._qubits) return set() def to_dict(self) -> dict: diff --git a/src/braket/circuits/noises.py b/src/braket/circuits/noises.py index 904f7ac74..a8829f1a4 100644 --- a/src/braket/circuits/noises.py +++ b/src/braket/circuits/noises.py @@ -953,9 +953,10 @@ def from_dict(cls, noise: dict) -> Noise: Returns: Noise: A Noise object that represents the passed in dictionary. """ - probabilities = {} - for pauli_string, prob in noise["probabilities"].items(): - probabilities[pauli_string] = _parameter_from_dict(prob) + probabilities = { + pauli_string: _parameter_from_dict(prob) + for pauli_string, prob in noise["probabilities"].items() + } return TwoQubitPauliChannel(probabilities=probabilities) @@ -1327,7 +1328,7 @@ def __init__(self, matrices: Iterable[np.ndarray], display_name: str = "KR"): """ for matrix in matrices: verify_quantum_operator_matrix_dimensions(matrix) - if not int(np.log2(matrix.shape[0])) == int(np.log2(matrices[0].shape[0])): + if int(np.log2(matrix.shape[0])) != int(np.log2(matrices[0].shape[0])): raise ValueError(f"all matrices in {matrices} must have the same shape") self._matrices = [np.array(matrix, dtype=complex) for matrix in matrices] self._display_name = display_name @@ -1449,11 +1450,10 @@ def _ascii_representation( Returns: str: The ascii representation of the noise. """ - param_list = [] - for param in parameters: - param_list.append( - str(param) if isinstance(param, FreeParameterExpression) else f"{param:.2g}" - ) + param_list = [ + (str(param) if isinstance(param, FreeParameterExpression) else f"{param:.2g}") + for param in parameters + ] param_str = ",".join(param_list) return f"{noise}({param_str})" diff --git a/src/braket/circuits/observables.py b/src/braket/circuits/observables.py index 2d5ccdb68..ae4f36a09 100644 --- a/src/braket/circuits/observables.py +++ b/src/braket/circuits/observables.py @@ -67,7 +67,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.Ry(-math.pi / 4)]) # noqa: C409 + return (Gate.Ry(-math.pi / 4),) Observable.register_observable(H) @@ -155,7 +155,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.H()]) # noqa: C409 + return (Gate.H(),) Observable.register_observable(X) @@ -193,7 +193,7 @@ def to_matrix(self) -> np.ndarray: @property def basis_rotation_gates(self) -> tuple[Gate, ...]: - return tuple([Gate.Z(), Gate.S(), Gate.H()]) # noqa: C409 + return Gate.Z(), Gate.S(), Gate.H() Observable.register_observable(Y) @@ -266,15 +266,14 @@ def __init__(self, observables: list[Observable]): flattened_observables = [] for obs in observables: if isinstance(obs, TensorProduct): - for nested_obs in obs.factors: - flattened_observables.append(nested_obs) + flattened_observables.extend(iter(obs.factors)) # make sure you don't lose coefficient of tensor product flattened_observables[-1] *= obs.coefficient elif isinstance(obs, Sum): raise TypeError("Sum observables not allowed in TensorProduct") else: flattened_observables.append(obs) - qubit_count = sum([obs.qubit_count for obs in flattened_observables]) + qubit_count = sum(obs.qubit_count for obs in flattened_observables) # aggregate all coefficients for the product, since aX @ bY == ab * X @ Y coefficient = np.prod([obs.coefficient for obs in flattened_observables]) unscaled_factors = tuple(obs._unscaled() for obs in flattened_observables) @@ -447,8 +446,7 @@ def __init__(self, observables: list[Observable], display_name: str = "Hamiltoni flattened_observables = [] for obs in observables: if isinstance(obs, Sum): - for nested_obs in obs.summands: - flattened_observables.append(nested_obs) + flattened_observables.extend(iter(obs.summands)) else: flattened_observables.append(obs) @@ -661,9 +659,8 @@ def observable_from_ir(ir_observable: list[Union[str, list[list[list[float]]]]]) """ if len(ir_observable) == 1: return _observable_from_ir_list_item(ir_observable[0]) - else: - observable = TensorProduct([_observable_from_ir_list_item(obs) for obs in ir_observable]) - return observable + observable = TensorProduct([_observable_from_ir_list_item(obs) for obs in ir_observable]) + return observable def _observable_from_ir_list_item(observable: Union[str, list[list[list[float]]]]) -> Observable: @@ -684,4 +681,4 @@ def _observable_from_ir_list_item(observable: Union[str, list[list[list[float]]] ) return Hermitian(matrix) except Exception as e: - raise ValueError(f"Invalid observable specified: {observable} error: {e}") + raise ValueError(f"Invalid observable specified: {observable} error: {e}") from e diff --git a/src/braket/circuits/quantum_operator.py b/src/braket/circuits/quantum_operator.py index 068c4171d..b706e1822 100644 --- a/src/braket/circuits/quantum_operator.py +++ b/src/braket/circuits/quantum_operator.py @@ -52,12 +52,12 @@ def __init__(self, qubit_count: Optional[int], ascii_symbols: Sequence[str]): fixed_qubit_count = self.fixed_qubit_count() if fixed_qubit_count is NotImplemented: self._qubit_count = qubit_count + elif qubit_count and qubit_count != fixed_qubit_count: + raise ValueError( + f"Provided qubit count {qubit_count}" + "does not equal fixed qubit count {fixed_qubit_count}" + ) else: - if qubit_count and qubit_count != fixed_qubit_count: - raise ValueError( - f"Provided qubit count {qubit_count}" - "does not equal fixed qubit count {fixed_qubit_count}" - ) self._qubit_count = fixed_qubit_count if not isinstance(self._qubit_count, int): diff --git a/src/braket/circuits/quantum_operator_helpers.py b/src/braket/circuits/quantum_operator_helpers.py index 15cb8d1fd..10c22808e 100644 --- a/src/braket/circuits/quantum_operator_helpers.py +++ b/src/braket/circuits/quantum_operator_helpers.py @@ -99,7 +99,7 @@ def is_cptp(matrices: Iterable[np.ndarray]) -> bool: Returns: bool: If the matrices define a CPTP map. """ - E = sum([np.dot(matrix.T.conjugate(), matrix) for matrix in matrices]) + E = sum(np.dot(matrix.T.conjugate(), matrix) for matrix in matrices) return np.allclose(E, np.eye(*E.shape)) diff --git a/src/braket/circuits/result_types.py b/src/braket/circuits/result_types.py index 0b73c5e7b..325fa8f46 100644 --- a/src/braket/circuits/result_types.py +++ b/src/braket/circuits/result_types.py @@ -68,9 +68,7 @@ def state_vector() -> ResultType: return ResultType.StateVector() def __eq__(self, other: StateVector) -> bool: - if isinstance(other, StateVector): - return True - return False + return isinstance(other, StateVector) def __copy__(self) -> StateVector: return type(self)() @@ -334,9 +332,7 @@ def amplitude(state: list[str]) -> ResultType: return ResultType.Amplitude(state=state) def __eq__(self, other: Amplitude): - if isinstance(other, Amplitude): - return self.state == other.state - return False + return self.state == other.state if isinstance(other, Amplitude) else False def __repr__(self): return f"Amplitude(state={self.state})" @@ -424,9 +420,7 @@ def probability(target: QubitSetInput | None = None) -> ResultType: return ResultType.Probability(target=target) def __eq__(self, other: Probability) -> bool: - if isinstance(other, Probability): - return self.target == other.target - return False + return self.target == other.target if isinstance(other, Probability) else False def __repr__(self) -> str: return f"Probability(target={self.target})" diff --git a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py index a633d318c..4a7c9565c 100644 --- a/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/ascii_circuit_diagram.py @@ -124,9 +124,7 @@ def _create_diagram_column( target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = { - qubit: state for qubit, state in zip(control_qubits, control_state) - } + map_control_qubit_states = dict(zip(control_qubits, control_state)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) @@ -188,8 +186,12 @@ def _draw_symbol( str: a string representing the symbol. """ connection_char = cls._vertical_delimiter() if connection in ["above"] else " " - output = "{0:{width}}\n".format(connection_char, width=symbols_width + 1) - output += "{0:{fill}{align}{width}}\n".format( - symbol, fill=cls._qubit_line_character(), align="<", width=symbols_width + 1 + output = "{0:{width}}\n".format( + connection_char, width=symbols_width + 1 + ) + "{0:{fill}{align}{width}}\n".format( + symbol, + fill=cls._qubit_line_character(), + align="<", + width=symbols_width + 1, ) return output diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py index e1dda5a3b..ad30b34a7 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram.py @@ -244,7 +244,7 @@ def _create_output( Returns: str: a string representing a diagram column. """ - symbols_width = max([len(symbol) for symbol in symbols.values()]) + cls._box_pad() + symbols_width = max(len(symbol) for symbol in symbols.values()) + cls._box_pad() output = "" if global_phase is not None: diff --git a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py index 83763a9ae..f261b00b6 100644 --- a/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py +++ b/src/braket/circuits/text_diagram_builders/text_circuit_diagram_utils.py @@ -103,14 +103,15 @@ def _compute_moment_global_phase( Returns: float | None: The updated integrated phase. """ - moment_phase = 0 - for item in items: + moment_phase = sum( + item.operator.angle + for item in items if ( isinstance(item, Instruction) and isinstance(item.operator, Gate) and item.operator.name == "GPhase" - ): - moment_phase += item.operator.angle + ) + ) return global_phase + moment_phase if global_phase is not None else None diff --git a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py index 0739724e9..85567de28 100644 --- a/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py +++ b/src/braket/circuits/text_diagram_builders/unicode_circuit_diagram.py @@ -165,9 +165,7 @@ def _build_parameters( target_qubits = item.target control_qubits = getattr(item, "control", QubitSet()) control_state = getattr(item, "control_state", "1" * len(control_qubits)) - map_control_qubit_states = { - qubit: state for qubit, state in zip(control_qubits, control_state) - } + map_control_qubit_states = dict(zip(control_qubits, control_state)) target_and_control = target_qubits.union(control_qubits) qubits = QubitSet(range(min(target_and_control), max(target_and_control) + 1)) @@ -213,7 +211,7 @@ def _draw_symbol( """ top = "" bottom = "" - if symbol in ["C", "N", "SWAP"]: + if symbol in {"C", "N", "SWAP"}: if connection in ["above", "both"]: top = _fill_symbol(cls._vertical_delimiter(), " ") if connection in ["below", "both"]: @@ -227,10 +225,7 @@ def _draw_symbol( elif symbol == "┼": top = bottom = _fill_symbol(cls._vertical_delimiter(), " ") symbol = _fill_symbol(f"{symbol}", cls._qubit_line_character()) - elif symbol == cls._qubit_line_character(): - # We do not box when no gate is applied. - pass - else: + elif symbol != cls._qubit_line_character(): top, symbol, bottom = cls._build_box(symbol, connection) output = f"{_fill_symbol(top, ' ', symbols_width)} \n" diff --git a/src/braket/devices/device.py b/src/braket/devices/device.py index 3f2a28e41..dbc7b6b35 100644 --- a/src/braket/devices/device.py +++ b/src/braket/devices/device.py @@ -117,12 +117,12 @@ def status(self) -> str: return self._status def _validate_device_noise_model_support(self, noise_model: NoiseModel) -> None: - supported_noises = set( + supported_noises = { SUPPORTED_NOISE_PRAGMA_TO_NOISE[pragma].__name__ for pragma in self.properties.action[DeviceActionType.OPENQASM].supportedPragmas if pragma in SUPPORTED_NOISE_PRAGMA_TO_NOISE - ) - noise_operators = set(noise_instr.noise.name for noise_instr in noise_model._instructions) + } + noise_operators = {noise_instr.noise.name for noise_instr in noise_model._instructions} if not noise_operators <= supported_noises: raise ValueError( f"{self.name} does not support noise simulation or the noise model includes noise " diff --git a/src/braket/devices/local_simulator.py b/src/braket/devices/local_simulator.py index 69fcfdaff..1dec56d37 100644 --- a/src/braket/devices/local_simulator.py +++ b/src/braket/devices/local_simulator.py @@ -168,9 +168,8 @@ def run_batch( # noqa: C901 single_input = isinstance(inputs, dict) - if not single_task and not single_input: - if len(task_specifications) != len(inputs): - raise ValueError("Multiple inputs and task specifications must be equal in number.") + if not single_task and not single_input and len(task_specifications) != len(inputs): + raise ValueError("Multiple inputs and task specifications must be equal in number.") if single_task: task_specifications = repeat(task_specifications) @@ -187,8 +186,7 @@ def run_batch( # noqa: C901 for task_specification, input_map in tasks_and_inputs: if isinstance(task_specification, Circuit): param_names = {param.name for param in task_specification.parameters} - unbounded_parameters = param_names - set(input_map.keys()) - if unbounded_parameters: + if unbounded_parameters := param_names - set(input_map.keys()): raise ValueError( f"Cannot execute circuit with unbound parameters: " f"{unbounded_parameters}" @@ -237,13 +235,12 @@ def _get_simulator(self, simulator: Union[str, BraketSimulator]) -> LocalSimulat @_get_simulator.register def _(self, backend_name: str): - if backend_name in _simulator_devices: - device_class = _simulator_devices[backend_name].load() - return device_class() - else: + if backend_name not in _simulator_devices: raise ValueError( f"Only the following devices are available {_simulator_devices.keys()}" ) + device_class = _simulator_devices[backend_name].load() + return device_class() @_get_simulator.register def _(self, backend_impl: BraketSimulator): diff --git a/src/braket/ipython_utils.py b/src/braket/ipython_utils.py index c443d1b44..d850ee85c 100644 --- a/src/braket/ipython_utils.py +++ b/src/braket/ipython_utils.py @@ -23,8 +23,6 @@ def running_in_jupyter() -> bool: bool: True if running in Jupyter, else False. """ in_ipython = False - in_ipython_kernel = False - # if IPython hasn't been imported, there's nothing to check if "IPython" in sys.modules: get_ipython = sys.modules["IPython"].__dict__["get_ipython"] @@ -32,7 +30,4 @@ def running_in_jupyter() -> bool: ip = get_ipython() in_ipython = ip is not None - if in_ipython: - in_ipython_kernel = getattr(ip, "kernel", None) is not None - - return in_ipython_kernel + return getattr(ip, "kernel", None) is not None if in_ipython else False diff --git a/src/braket/jobs/environment_variables.py b/src/braket/jobs/environment_variables.py index ad42006de..6d7d18364 100644 --- a/src/braket/jobs/environment_variables.py +++ b/src/braket/jobs/environment_variables.py @@ -44,9 +44,7 @@ def get_input_data_dir(channel: str = "input") -> str: str: The input directory, defaulting to current working directory. """ input_dir = os.getenv("AMZN_BRAKET_INPUT_DIR", ".") - if input_dir != ".": - return f"{input_dir}/{channel}" - return input_dir + return f"{input_dir}/{channel}" if input_dir != "." else input_dir def get_results_dir() -> str: diff --git a/src/braket/jobs/hybrid_job.py b/src/braket/jobs/hybrid_job.py index 3cd622e37..77f5f43d0 100644 --- a/src/braket/jobs/hybrid_job.py +++ b/src/braket/jobs/hybrid_job.py @@ -247,7 +247,7 @@ def _validate_python_version(image_uri: str | None, aws_session: AwsSession | No image_uri = image_uri or retrieve_image(Framework.BASE, aws_session.region) tag = aws_session.get_full_image_tag(image_uri) major_version, minor_version = re.search(r"-py(\d)(\d+)-", tag).groups() - if not (sys.version_info.major, sys.version_info.minor) == ( + if (sys.version_info.major, sys.version_info.minor) != ( int(major_version), int(minor_version), ): @@ -369,9 +369,7 @@ def _process_input_data(input_data: dict) -> list[str]: input_data = {"input": input_data} def matches(prefix: str) -> list[str]: - return [ - str(path) for path in Path(prefix).parent.iterdir() if str(path).startswith(str(prefix)) - ] + return [str(path) for path in Path(prefix).parent.iterdir() if str(path).startswith(prefix)] def is_prefix(path: str) -> bool: return len(matches(path)) > 1 or not Path(path).exists() @@ -388,7 +386,7 @@ def is_prefix(path: str) -> bool: f"the working directory. Use `get_input_data_dir({channel_arg})` to read " f"input data from S3 source inside the job container." ) - elif is_prefix(data): + elif is_prefix(str(data)): prefix_channels.add(channel) elif Path(data).is_dir(): directory_channels.add(channel) diff --git a/src/braket/jobs/local/local_job.py b/src/braket/jobs/local/local_job.py index af6806157..4dd15607a 100644 --- a/src/braket/jobs/local/local_job.py +++ b/src/braket/jobs/local/local_job.py @@ -211,8 +211,10 @@ def run_log(self) -> str: try: with open(os.path.join(self.name, "log.txt")) as log_file: self._run_log = log_file.read() - except FileNotFoundError: - raise ValueError(f"Unable to find logs in the local job directory {self.name}.") + except FileNotFoundError as e: + raise ValueError( + f"Unable to find logs in the local job directory {self.name}." + ) from e return self._run_log def state(self, use_cached_value: bool = False) -> str: @@ -241,7 +243,6 @@ def metadata(self, use_cached_value: bool = False) -> dict[str, Any]: Returns: dict[str, Any]: None """ - pass def cancel(self) -> str: """When running the hybrid job in local mode, the cancelling a running is not possible. @@ -249,7 +250,6 @@ def cancel(self) -> str: Returns: str: None """ - pass def download_result( self, @@ -268,7 +268,6 @@ def download_result( poll_interval_seconds (float): The polling interval, in seconds, for `result()`. Default: 5 seconds. """ - pass def result( self, @@ -296,8 +295,10 @@ def result( persisted_data.dataDictionary, persisted_data.dataFormat ) return deserialized_data - except FileNotFoundError: - raise ValueError(f"Unable to find results in the local job directory {self.name}.") + except FileNotFoundError as e: + raise ValueError( + f"Unable to find results in the local job directory {self.name}." + ) from e def metrics( self, diff --git a/src/braket/jobs/local/local_job_container.py b/src/braket/jobs/local/local_job_container.py index 04aeaff1a..6d9d08f4f 100644 --- a/src/braket/jobs/local/local_job_container.py +++ b/src/braket/jobs/local/local_job_container.py @@ -138,8 +138,8 @@ def _pull_image(self, image_uri: str) -> None: "Please pull down the container, or specify a valid ECR URL, " "before proceeding." ) - ecr_url = ecr_pattern_match.group(1) - account_id = ecr_pattern_match.group(2) + ecr_url = ecr_pattern_match[1] + account_id = ecr_pattern_match[2] self._login_to_ecr(account_id, ecr_url) self._logger.warning("Pulling docker container image. This may take a while.") subprocess.run(["docker", "pull", image_uri]) diff --git a/src/braket/jobs/local/local_job_container_setup.py b/src/braket/jobs/local/local_job_container_setup.py index 57c1f3653..65cef387c 100644 --- a/src/braket/jobs/local/local_job_container_setup.py +++ b/src/braket/jobs/local/local_job_container_setup.py @@ -41,7 +41,7 @@ def setup_container( logger = getLogger(__name__) _create_expected_paths(container, **creation_kwargs) run_environment_variables = {} - run_environment_variables.update(_get_env_credentials(aws_session, logger)) + run_environment_variables |= _get_env_credentials(aws_session, logger) run_environment_variables.update( _get_env_script_mode_config(creation_kwargs["algorithmSpecification"]["scriptModeConfig"]) ) @@ -222,8 +222,10 @@ def _download_input_data( found_item = False try: Path(download_dir, channel_name).mkdir() - except FileExistsError: - raise ValueError(f"Duplicate channel names not allowed for input data: {channel_name}") + except FileExistsError as e: + raise ValueError( + f"Duplicate channel names not allowed for input data: {channel_name}" + ) from e for s3_key in s3_keys: relative_key = Path(s3_key).relative_to(top_level) download_path = Path(download_dir, channel_name, relative_key) diff --git a/src/braket/jobs/logs.py b/src/braket/jobs/logs.py index 5e2f12f82..9aa7dfaca 100644 --- a/src/braket/jobs/logs.py +++ b/src/braket/jobs/logs.py @@ -210,9 +210,9 @@ def flush_log_streams( # noqa C901 if s["logStreamName"] not in stream_names ] stream_names.extend(new_streams) - positions.update( - [(s, Position(timestamp=0, skip=0)) for s in stream_names if s not in positions] - ) + positions |= [ + (s, Position(timestamp=0, skip=0)) for s in stream_names if s not in positions + ] except ClientError as e: # On the very first training job run on an account, there's no # log group until the container starts logging, so ignore any @@ -221,7 +221,7 @@ def flush_log_streams( # noqa C901 if err.get("Code") != "ResourceNotFoundException": raise - if len(stream_names) > 0: + if stream_names: if not has_streams: print() has_streams = True diff --git a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py index b2d12fe36..8f5d3dcd5 100644 --- a/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_insights_metrics_fetcher.py @@ -105,8 +105,7 @@ def _parse_log_line(self, result_entry: list[dict[str, Any]], parser: LogMetrics and other metadata that we (currently) do not use. parser (LogMetricsParser) : The CWL metrics parser. """ - message = self._get_element_from_log_line("@message", result_entry) - if message: + if message := self._get_element_from_log_line("@message", result_entry): timestamp = self._get_element_from_log_line("@timestamp", result_entry) parser.parse_log_message(timestamp, message) diff --git a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py index 8a5a5333d..e8da4ff89 100644 --- a/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py +++ b/src/braket/jobs/metrics_data/cwl_metrics_fetcher.py @@ -53,9 +53,7 @@ def _is_metrics_message(message: str) -> bool: Returns: bool: True if the given message is designated as containing Metrics; False otherwise. """ - if message: - return "Metrics -" in message - return False + return "Metrics -" in message if message else False def _parse_metrics_from_log_stream( self, @@ -105,21 +103,19 @@ def _get_log_streams_for_job(self, job_name: str, timeout_time: float) -> list[s """ kwargs = { "logGroupName": self.LOG_GROUP_NAME, - "logStreamNamePrefix": job_name + "/algo-", + "logStreamNamePrefix": f"{job_name}/algo-", } log_streams = [] while time.time() < timeout_time: response = self._logs_client.describe_log_streams(**kwargs) - streams = response.get("logStreams") - if streams: + if streams := response.get("logStreams"): for stream in streams: - name = stream.get("logStreamName") - if name: + if name := stream.get("logStreamName"): log_streams.append(name) - next_token = response.get("nextToken") - if not next_token: + if next_token := response.get("nextToken"): + kwargs["nextToken"] = next_token + else: return log_streams - kwargs["nextToken"] = next_token self._logger.warning("Timed out waiting for all metrics. Data may be incomplete.") return log_streams diff --git a/src/braket/jobs/metrics_data/exceptions.py b/src/braket/jobs/metrics_data/exceptions.py index 677a3a447..41cbf0491 100644 --- a/src/braket/jobs/metrics_data/exceptions.py +++ b/src/braket/jobs/metrics_data/exceptions.py @@ -14,5 +14,3 @@ class MetricsRetrievalError(Exception): """Raised when retrieving metrics fails.""" - - pass diff --git a/src/braket/jobs/metrics_data/log_metrics_parser.py b/src/braket/jobs/metrics_data/log_metrics_parser.py index 82142a589..1ff5b4d49 100644 --- a/src/braket/jobs/metrics_data/log_metrics_parser.py +++ b/src/braket/jobs/metrics_data/log_metrics_parser.py @@ -101,8 +101,7 @@ def parse_log_message(self, timestamp: str, message: str) -> None: return if timestamp and self.TIMESTAMP not in parsed_metrics: parsed_metrics[self.TIMESTAMP] = timestamp - node_match = self.NODE_TAG.match(message) - if node_match: + if node_match := self.NODE_TAG.match(message): parsed_metrics[self.NODE_ID] = node_match.group(1) self.all_metrics.append(parsed_metrics) diff --git a/src/braket/jobs/quantum_job_creation.py b/src/braket/jobs/quantum_job_creation.py index 98905dc56..3c4a01b5c 100644 --- a/src/braket/jobs/quantum_job_creation.py +++ b/src/braket/jobs/quantum_job_creation.py @@ -224,7 +224,7 @@ def prepare_quantum_job( "sagemaker_distributed_dataparallel_enabled": "true", "sagemaker_instance_type": instance_config.instanceType, } - hyperparameters.update(distributed_hyperparams) + hyperparameters |= distributed_hyperparams create_job_kwargs = { "jobName": job_name, @@ -241,16 +241,12 @@ def prepare_quantum_job( } if reservation_arn: - create_job_kwargs.update( + create_job_kwargs["associations"] = [ { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", } - ) + ] return create_job_kwargs @@ -268,11 +264,11 @@ def _generate_default_job_name( Returns: str: Hybrid job name. """ - max_length = 50 timestamp = timestamp if timestamp is not None else str(int(time.time() * 1000)) if func: name = func.__name__.replace("_", "-") + max_length = 50 if len(name) + len(timestamp) > max_length: name = name[: max_length - len(timestamp) - 1] warnings.warn( @@ -339,8 +335,8 @@ def _process_local_source_module( try: # raises FileNotFoundError if not found abs_path_source_module = Path(source_module).resolve(strict=True) - except FileNotFoundError: - raise ValueError(f"Source module not found: {source_module}") + except FileNotFoundError as e: + raise ValueError(f"Source module not found: {source_module}") from e entry_point = entry_point or abs_path_source_module.stem _validate_entry_point(abs_path_source_module, entry_point) @@ -366,9 +362,8 @@ def _validate_entry_point(source_module_path: Path, entry_point: str) -> None: module = importlib.util.find_spec(importable, source_module_path.stem) if module is None: raise AssertionError - # if entry point is nested (ie contains '.'), parent modules are imported - except (ModuleNotFoundError, AssertionError): - raise ValueError(f"Entry point module was not found: {importable}") + except (ModuleNotFoundError, AssertionError) as e: + raise ValueError(f"Entry point module was not found: {importable}") from e finally: sys.path.pop() @@ -460,21 +455,20 @@ def _process_channel( """ if AwsSession.is_s3_uri(location): return S3DataSourceConfig(location) - else: - # local prefix "path/to/prefix" will be mapped to - # s3://bucket/jobs/job-name/subdirectory/data/input/prefix - location_name = Path(location).name - s3_prefix = AwsSession.construct_s3_uri( - aws_session.default_bucket(), - "jobs", - job_name, - subdirectory, - "data", - channel_name, - location_name, - ) - aws_session.upload_local_data(location, s3_prefix) - return S3DataSourceConfig(s3_prefix) + # local prefix "path/to/prefix" will be mapped to + # s3://bucket/jobs/job-name/subdirectory/data/input/prefix + location_name = Path(location).name + s3_prefix = AwsSession.construct_s3_uri( + aws_session.default_bucket(), + "jobs", + job_name, + subdirectory, + "data", + channel_name, + location_name, + ) + aws_session.upload_local_data(location, s3_prefix) + return S3DataSourceConfig(s3_prefix) def _convert_input_to_config(input_data: dict[str, S3DataSourceConfig]) -> list[dict[str, Any]]: diff --git a/src/braket/parametric/free_parameter.py b/src/braket/parametric/free_parameter.py index 78fbe45b3..1f3a69e72 100644 --- a/src/braket/parametric/free_parameter.py +++ b/src/braket/parametric/free_parameter.py @@ -66,7 +66,7 @@ def subs(self, parameter_values: dict[str, Number]) -> Union[FreeParameter, Numb Union[FreeParameter, Number]: The substituted value if this parameter is in parameter_values, otherwise returns self """ - return parameter_values[self.name] if self.name in parameter_values else self + return parameter_values.get(self.name, self) def __str__(self): return str(self.name) @@ -92,7 +92,7 @@ def _set_name(self, name: str) -> None: raise ValueError("FreeParameter names must be non empty") if not isinstance(name, str): raise TypeError("FreeParameter names must be strings") - if not name[0].isalpha() and not name[0] == "_": + if not name[0].isalpha() and name[0] != "_": raise ValueError("FreeParameter names must start with a letter or an underscore") self._name = Symbol(name) diff --git a/src/braket/pulse/ast/approximation_parser.py b/src/braket/pulse/ast/approximation_parser.py index f7ec9d3bf..d2dcf65e8 100644 --- a/src/braket/pulse/ast/approximation_parser.py +++ b/src/braket/pulse/ast/approximation_parser.py @@ -151,9 +151,7 @@ def visit_ClassicalDeclaration( context.variables[identifier] = self.visit(node.init_expression, context) elif type(node.type) == ast.FrameType: pass - elif type(node.type) == ast.PortType: - pass - else: + elif type(node.type) != ast.PortType: raise NotImplementedError def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseState) -> None: @@ -171,7 +169,7 @@ def visit_DelayInstruction(self, node: ast.DelayInstruction, context: _ParseStat # barrier without arguments is applied to all the frames of the context frames = list(context.frame_data.keys()) dts = [context.frame_data[frame_id].dt for frame_id in frames] - max_time = max([context.frame_data[frame_id].current_time for frame_id in frames]) + max_time = max(context.frame_data[frame_id].current_time for frame_id in frames) # All frames are delayed till the first multiple of the LCM([port.dts]) # after the longest time of all considered frames lcm = _lcm_floats(*dts) @@ -198,7 +196,7 @@ def visit_QuantumBarrier(self, node: ast.QuantumBarrier, context: _ParseState) - # barrier without arguments is applied to all the frames of the context frames = list(context.frame_data.keys()) dts = [context.frame_data[frame_id].dt for frame_id in frames] - max_time = max([context.frame_data[frame_id].current_time for frame_id in frames]) + max_time = max(context.frame_data[frame_id].current_time for frame_id in frames) # All frames are delayed till the first multiple of the LCM([port.dts]) # after the longest time of all considered frames lcm = _lcm_floats(*dts) @@ -391,7 +389,7 @@ def visit_BooleanLiteral(self, node: ast.BooleanLiteral, context: _ParseState) - Returns: bool: The parsed boolean value. """ - return True if node.value else False + return bool(node.value) def visit_DurationLiteral(self, node: ast.DurationLiteral, context: _ParseState) -> float: """Visit Duration Literal. @@ -477,7 +475,6 @@ def capture_v0(self, node: ast.FunctionCall, context: _ParseState) -> None: node (ast.FunctionCall): The function call node. context (_ParseState): The parse state. """ - pass def play(self, node: ast.FunctionCall, context: _ParseState) -> None: """A 'play' Function call. @@ -554,17 +551,16 @@ def drag_gaussian(self, node: ast.FunctionCall, context: _ParseState) -> Wavefor def _init_frame_data(frames: dict[str, Frame]) -> dict[str, _FrameState]: - frame_states = {} - for frameId, frame in frames.items(): - frame_states[frameId] = _FrameState( - frame.port.dt, frame.frequency, frame.phase % (2 * np.pi) - ) + frame_states = { + frameId: _FrameState(frame.port.dt, frame.frequency, frame.phase % (2 * np.pi)) + for frameId, frame in frames.items() + } return frame_states def _init_qubit_frame_mapping(frames: dict[str, Frame]) -> dict[str, list[str]]: mapping = {} - for frameId in frames.keys(): + for frameId in frames: if m := ( re.search(r"q(\d+)_q(\d+)_[a-z_]+", frameId) or re.search(r"[rq](\d+)_[a-z_]+", frameId) ): diff --git a/src/braket/pulse/ast/free_parameters.py b/src/braket/pulse/ast/free_parameters.py index 41c541da8..1750275ac 100644 --- a/src/braket/pulse/ast/free_parameters.py +++ b/src/braket/pulse/ast/free_parameters.py @@ -61,12 +61,12 @@ def visit_BinaryExpression( """ lhs = self.visit(node.lhs) rhs = self.visit(node.rhs) - ops = { - ast.BinaryOperator["+"]: operator.add, - ast.BinaryOperator["*"]: operator.mul, - ast.BinaryOperator["**"]: operator.pow, - } if isinstance(lhs, ast.FloatLiteral): + ops = { + ast.BinaryOperator["+"]: operator.add, + ast.BinaryOperator["*"]: operator.mul, + ast.BinaryOperator["**"]: operator.pow, + } if isinstance(rhs, ast.FloatLiteral): return ast.FloatLiteral(ops[node.op](lhs.value, rhs.value)) elif isinstance(rhs, ast.DurationLiteral) and node.op == ast.BinaryOperator["*"]: diff --git a/src/braket/pulse/ast/qasm_transformer.py b/src/braket/pulse/ast/qasm_transformer.py index 3d7adb4a3..40a6d25d5 100644 --- a/src/braket/pulse/ast/qasm_transformer.py +++ b/src/braket/pulse/ast/qasm_transformer.py @@ -39,22 +39,21 @@ def visit_ExpressionStatement(self, expression_statement: ast.ExpressionStatemen Any: The expression statement. """ if ( - isinstance(expression_statement.expression, ast.FunctionCall) - and expression_statement.expression.name.name == "capture_v0" - and self._register_identifier + not isinstance(expression_statement.expression, ast.FunctionCall) + or expression_statement.expression.name.name != "capture_v0" + or not self._register_identifier ): - # For capture_v0 nodes, it replaces it with classical assignment statements - # of the form: - # b[0] = capture_v0(...) - # b[1] = capture_v0(...) - new_val = ast.ClassicalAssignment( - # Ideally should use IndexedIdentifier here, but this works since it is just - # for printing. - ast.Identifier(name=f"{self._register_identifier}[{self._capture_v0_count}]"), - ast.AssignmentOperator["="], - expression_statement.expression, - ) - self._capture_v0_count += 1 - return new_val - else: return expression_statement + # For capture_v0 nodes, it replaces it with classical assignment statements + # of the form: + # b[0] = capture_v0(...) + # b[1] = capture_v0(...) + new_val = ast.ClassicalAssignment( + # Ideally should use IndexedIdentifier here, but this works since it is just + # for printing. + ast.Identifier(name=f"{self._register_identifier}[{self._capture_v0_count}]"), + ast.AssignmentOperator["="], + expression_statement.expression, + ) + self._capture_v0_count += 1 + return new_val diff --git a/src/braket/pulse/pulse_sequence.py b/src/braket/pulse/pulse_sequence.py index a788b38c0..9d43127a0 100644 --- a/src/braket/pulse/pulse_sequence.py +++ b/src/braket/pulse/pulse_sequence.py @@ -345,7 +345,7 @@ def _parse_arg_from_calibration_schema( "waveform": waveforms.get, "expr": FreeParameterExpression, } - if argument["type"] in nonprimitive_arg_type.keys(): + if argument["type"] in nonprimitive_arg_type: return nonprimitive_arg_type[argument["type"]](argument["value"]) else: return getattr(builtins, argument["type"])(argument["value"]) @@ -369,40 +369,37 @@ def _parse_from_calibration_schema( """ calibration_sequence = cls() for instr in calibration: - if hasattr(PulseSequence, f"{instr['name']}"): - instr_function = getattr(calibration_sequence, instr["name"]) - instr_args_keys = signature(instr_function).parameters.keys() - instr_args = {} - if instr["arguments"] is not None: - for argument in instr["arguments"]: - if argument["name"] in {"qubit", "frame"} and instr["name"] in { - "barrier", - "delay", - }: - argument_value = ( - [frames[argument["value"]]] - if argument["name"] == "frame" - else instr_args.get("qubits_or_frames", QubitSet()) - ) - # QubitSet is an IndexedSet so the ordering matters - if argument["name"] == "frame": - argument_value = ( - instr_args.get("qubits_or_frames", []) + argument_value - ) - else: - argument_value.update(QubitSet(int(argument["value"]))) - instr_args["qubits_or_frames"] = argument_value - elif argument["name"] in instr_args_keys: - instr_args[argument["name"]] = ( - calibration_sequence._parse_arg_from_calibration_schema( - argument, waveforms, frames - ) + if not hasattr(PulseSequence, f"{instr['name']}"): + raise ValueError(f"The {instr['name']} instruction has not been implemented") + instr_function = getattr(calibration_sequence, instr["name"]) + instr_args_keys = signature(instr_function).parameters.keys() + instr_args = {} + if instr["arguments"] is not None: + for argument in instr["arguments"]: + if argument["name"] in {"qubit", "frame"} and instr["name"] in { + "barrier", + "delay", + }: + argument_value = ( + [frames[argument["value"]]] + if argument["name"] == "frame" + else instr_args.get("qubits_or_frames", QubitSet()) + ) + # QubitSet is an IndexedSet so the ordering matters + if argument["name"] == "frame": + argument_value = instr_args.get("qubits_or_frames", []) + argument_value + else: + argument_value.update(QubitSet(int(argument["value"]))) + instr_args["qubits_or_frames"] = argument_value + elif argument["name"] in instr_args_keys: + instr_args[argument["name"]] = ( + calibration_sequence._parse_arg_from_calibration_schema( + argument, waveforms, frames ) - else: - instr_args["qubits_or_frames"] = [] - instr_function(**instr_args) + ) else: - raise ValueError(f"The {instr['name']} instruction has not been implemented") + instr_args["qubits_or_frames"] = [] + instr_function(**instr_args) return calibration_sequence def __call__( diff --git a/src/braket/pulse/waveforms.py b/src/braket/pulse/waveforms.py index 9ca43050a..915d187a8 100644 --- a/src/braket/pulse/waveforms.py +++ b/src/braket/pulse/waveforms.py @@ -512,10 +512,9 @@ def _parse_waveform_from_calibration_schema(waveform: dict) -> Waveform: "gaussian": GaussianWaveform._from_calibration_schema, "constant": ConstantWaveform._from_calibration_schema, } - if "amplitudes" in waveform.keys(): + if "amplitudes" in waveform: waveform["name"] = "arbitrary" if waveform["name"] in waveform_names: return waveform_names[waveform["name"]](waveform) - else: - id = waveform["waveformId"] - raise ValueError(f"The waveform {id} of cannot be constructed") + waveform_id = waveform["waveformId"] + raise ValueError(f"The waveform {waveform_id} of cannot be constructed") diff --git a/src/braket/quantum_information/pauli_string.py b/src/braket/quantum_information/pauli_string.py index 9064b942a..0de8e8e3b 100644 --- a/src/braket/quantum_information/pauli_string.py +++ b/src/braket/quantum_information/pauli_string.py @@ -208,7 +208,7 @@ def dot(self, other: PauliString, inplace: bool = False) -> PauliString: # ignore complex global phase if phase_result.real < 0 or phase_result.imag < 0: - pauli_result = "-" + pauli_result + pauli_result = f"-{pauli_result}" out_pauli_string = PauliString(pauli_result) if inplace: diff --git a/src/braket/registers/qubit.py b/src/braket/registers/qubit.py index 4be98b640..4c91ebd25 100644 --- a/src/braket/registers/qubit.py +++ b/src/braket/registers/qubit.py @@ -63,7 +63,4 @@ def new(qubit: QubitInput) -> Qubit: Returns: Qubit: The qubit. """ - if isinstance(qubit, Qubit): - return qubit - else: - return Qubit(qubit) + return qubit if isinstance(qubit, Qubit) else Qubit(qubit) diff --git a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py index 0bc110b2c..7bfb57eb3 100644 --- a/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py +++ b/src/braket/tasks/analog_hamiltonian_simulation_quantum_task_result.py @@ -129,7 +129,7 @@ def get_counts(self) -> dict[str, int]: 0 if pre_i == 0 else 1 if post_i == 0 else 2 for pre_i, post_i in zip(pre, post) ] state = "".join(states[s_idx] for s_idx in state_idx) - state_counts.update((state,)) + state_counts.update([state]) return dict(state_counts) diff --git a/src/braket/tasks/gate_model_quantum_task_result.py b/src/braket/tasks/gate_model_quantum_task_result.py index b64d4e009..81f90ae7b 100644 --- a/src/braket/tasks/gate_model_quantum_task_result.py +++ b/src/braket/tasks/gate_model_quantum_task_result.py @@ -121,11 +121,11 @@ def get_value_by_result_type(self, result_type: ResultType) -> Any: rt_hash = GateModelQuantumTaskResult._result_type_hash(rt_ir) result_type_index = self._result_types_indices[rt_hash] return self.values[result_type_index] - except KeyError: + except KeyError as e: raise ValueError( "Result type not found in result. " "Result types must be added to circuit before circuit is run on device." - ) + ) from e def __eq__(self, other: GateModelQuantumTaskResult) -> bool: if isinstance(other, GateModelQuantumTaskResult): @@ -159,9 +159,9 @@ def measurement_counts_from_measurements(measurements: np.ndarray) -> Counter: Counter: A Counter of measurements. Key is the measurements in a big endian binary string. Value is the number of times that measurement occurred. """ - bitstrings = [] - for j in range(len(measurements)): - bitstrings.append("".join([str(element) for element in measurements[j]])) + bitstrings = [ + "".join([str(element) for element in measurements[j]]) for j in range(len(measurements)) + ] return Counter(bitstrings) @staticmethod @@ -179,11 +179,11 @@ def measurement_probabilities_from_measurement_counts( dict[str, float]: A dictionary of probabilistic results. Key is the measurements in a big endian binary string. Value is the probability the measurement occurred. """ - measurement_probabilities = {} shots = sum(measurement_counts.values()) - for key, count in measurement_counts.items(): - measurement_probabilities[key] = count / shots + measurement_probabilities = { + key: count / shots for key, count in measurement_counts.items() + } return measurement_probabilities @staticmethod @@ -346,13 +346,14 @@ def cast_result_types(gate_model_task_result: GateModelTaskResult) -> None: if gate_model_task_result.resultTypes: for result_type in gate_model_task_result.resultTypes: type = result_type.type.type - if type == "probability": + if type == "amplitude": + for state in result_type.value: + result_type.value[state] = complex(*result_type.value[state]) + + elif type == "probability": result_type.value = np.array(result_type.value) elif type == "statevector": result_type.value = np.array([complex(*value) for value in result_type.value]) - elif type == "amplitude": - for state in result_type.value: - result_type.value[state] = complex(*result_type.value[state]) @staticmethod def _calculate_result_types( diff --git a/src/braket/timings/time_series.py b/src/braket/timings/time_series.py index 0023f32f5..67797d6c4 100644 --- a/src/braket/timings/time_series.py +++ b/src/braket/timings/time_series.py @@ -312,7 +312,7 @@ def periodic_signal(times: list[float], values: list[float], num_repeat: int = 1 Returns: TimeSeries: A new periodic time series. """ - if not (values[0] == values[-1]): + if values[0] != values[-1]: raise ValueError("The first and last values must coincide to guarantee periodicity") new_time_series = TimeSeries() diff --git a/test/integ_tests/gate_model_device_testing_utils.py b/test/integ_tests/gate_model_device_testing_utils.py index 41f20da8e..901cc819e 100644 --- a/test/integ_tests/gate_model_device_testing_utils.py +++ b/test/integ_tests/gate_model_device_testing_utils.py @@ -13,7 +13,7 @@ import concurrent.futures import math -from typing import Any, Dict, Union +from typing import Any, Union import numpy as np @@ -26,11 +26,11 @@ from braket.tasks import GateModelQuantumTaskResult -def get_tol(shots: int) -> Dict[str, float]: +def get_tol(shots: int) -> dict[str, float]: return {"atol": 0.2, "rtol": 0.3} if shots else {"atol": 0.01, "rtol": 0} -def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): +def qubit_ordering_testing(device: Device, run_kwargs: dict[str, Any]): # |110> should get back value of "110" state_110 = Circuit().x(0).x(1).i(2) result = device.run(state_110, **run_kwargs).result() @@ -51,8 +51,8 @@ def qubit_ordering_testing(device: Device, run_kwargs: Dict[str, Any]): def no_result_types_testing( program: Union[Circuit, OpenQasmProgram], device: Device, - run_kwargs: Dict[str, Any], - expected: Dict[str, float], + run_kwargs: dict[str, Any], + expected: dict[str, float], ): shots = run_kwargs["shots"] tol = get_tol(shots) @@ -63,14 +63,14 @@ def no_result_types_testing( assert len(result.measurements) == shots -def no_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def no_result_types_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): bell = Circuit().h(0).cnot(0, 1) bell_qasm = bell.to_ir(ir_type=IRType.OPENQASM) for task in (bell, bell_qasm): no_result_types_testing(task, device, run_kwargs, {"00": 0.5, "11": 0.5}) -def result_types_observable_not_in_instructions(device: Device, run_kwargs: Dict[str, Any]): +def result_types_observable_not_in_instructions(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) bell = ( @@ -90,7 +90,7 @@ def result_types_observable_not_in_instructions(device: Device, run_kwargs: Dict def result_types_zero_shots_bell_pair_testing( device: Device, include_state_vector: bool, - run_kwargs: Dict[str, Any], + run_kwargs: dict[str, Any], include_amplitude: bool = True, ): circuit = ( @@ -128,7 +128,7 @@ def result_types_zero_shots_bell_pair_testing( assert np.isclose(amplitude["11"], 1 / np.sqrt(2)) -def result_types_bell_pair_full_probability_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_bell_pair_full_probability_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) circuit = Circuit().h(0).cnot(0, 1).probability() @@ -143,7 +143,7 @@ def result_types_bell_pair_full_probability_testing(device: Device, run_kwargs: ) -def result_types_bell_pair_marginal_probability_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_bell_pair_marginal_probability_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) circuit = Circuit().h(0).cnot(0, 1).probability(0) @@ -158,7 +158,7 @@ def result_types_bell_pair_marginal_probability_testing(device: Device, run_kwar ) -def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): circuit = ( Circuit() .h(0) @@ -188,7 +188,7 @@ def result_types_nonzero_shots_bell_pair_testing(device: Device, run_kwargs: Dic def result_types_hermitian_testing( - device: Device, run_kwargs: Dict[str, Any], test_program: bool = True + device: Device, run_kwargs: dict[str, Any], test_program: bool = True ): shots = run_kwargs["shots"] theta = 0.543 @@ -202,7 +202,7 @@ def result_types_hermitian_testing( ) if shots: circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 0)) - tasks = (circuit,) if not test_program else (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) if test_program else (circuit,) for task in tasks: result = device.run(task, **run_kwargs).result() @@ -215,7 +215,7 @@ def result_types_hermitian_testing( def result_types_all_selected_testing( - device: Device, run_kwargs: Dict[str, Any], test_program: bool = True + device: Device, run_kwargs: dict[str, Any], test_program: bool = True ): shots = run_kwargs["shots"] theta = 0.543 @@ -231,7 +231,7 @@ def result_types_all_selected_testing( if shots: circuit.add_result_type(ResultType.Sample(Observable.Hermitian(array), 1)) - tasks = (circuit,) if not test_program else (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) + tasks = (circuit, circuit.to_ir(ir_type=IRType.OPENQASM)) if test_program else (circuit,) for task in tasks: result = device.run(task, **run_kwargs).result() @@ -280,7 +280,7 @@ def assert_variance_expectation_sample_result( assert np.allclose(variance, expected_var, **tol) -def result_types_tensor_x_y_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_x_y_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -308,7 +308,7 @@ def result_types_tensor_x_y_testing(device: Device, run_kwargs: Dict[str, Any]): ) -def result_types_tensor_z_z_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_z_z_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -329,7 +329,7 @@ def result_types_tensor_z_z_testing(device: Device, run_kwargs: Dict[str, Any]): ) -def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -359,7 +359,7 @@ def result_types_tensor_hermitian_hermitian_testing(device: Device, run_kwargs: ) -def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -386,7 +386,7 @@ def result_types_tensor_z_h_y_testing(device: Device, run_kwargs: Dict[str, Any] ) -def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -450,7 +450,7 @@ def result_types_tensor_z_hermitian_testing(device: Device, run_kwargs: Dict[str ) -def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] theta = 0.432 phi = 0.123 @@ -479,7 +479,7 @@ def result_types_tensor_y_hermitian_testing(device: Device, run_kwargs: Dict[str ) -def result_types_noncommuting_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_noncommuting_testing(device: Device, run_kwargs: dict[str, Any]): shots = 0 theta = 0.432 phi = 0.123 @@ -525,7 +525,7 @@ def result_types_noncommuting_testing(device: Device, run_kwargs: Dict[str, Any] assert np.allclose(result.values[3], expected_mean3) -def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs: Dict[str, Any]): +def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs: dict[str, Any]): circuit = ( Circuit() .h(0) @@ -540,7 +540,7 @@ def result_types_noncommuting_flipped_targets_testing(device: Device, run_kwargs assert np.allclose(result.values[1], np.sqrt(2) / 2) -def result_types_noncommuting_all(device: Device, run_kwargs: Dict[str, Any]): +def result_types_noncommuting_all(device: Device, run_kwargs: dict[str, Any]): array = np.array([[1, 2j], [-2j, 0]]) circuit = ( Circuit() @@ -556,7 +556,7 @@ def result_types_noncommuting_all(device: Device, run_kwargs: Dict[str, Any]): assert np.allclose(result.values[1], [0, 0]) -def multithreaded_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def multithreaded_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) bell = Circuit().h(0).cnot(0, 1) @@ -581,7 +581,7 @@ def run_circuit(circuit): assert len(result.measurements) == shots -def noisy_circuit_1qubit_noise_full_probability(device: Device, run_kwargs: Dict[str, Any]): +def noisy_circuit_1qubit_noise_full_probability(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) circuit = Circuit().x(0).x(1).bit_flip(0, 0.1).probability() @@ -596,7 +596,7 @@ def noisy_circuit_1qubit_noise_full_probability(device: Device, run_kwargs: Dict ) -def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: Dict[str, Any]): +def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) K0 = np.eye(4) * np.sqrt(0.9) @@ -615,7 +615,7 @@ def noisy_circuit_2qubit_noise_full_probability(device: Device, run_kwargs: Dict ) -def batch_bell_pair_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): +def batch_bell_pair_testing(device: AwsDevice, run_kwargs: dict[str, Any]): shots = run_kwargs["shots"] tol = get_tol(shots) circuits = [Circuit().h(0).cnot(0, 1) for _ in range(10)] @@ -630,7 +630,7 @@ def batch_bell_pair_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): assert [task.result() for task in batch.tasks] == results -def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): +def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: dict[str, Any]): openqasm_string = ( "OPENQASM 3;" "qubit[2] q;" @@ -649,7 +649,7 @@ def bell_pair_openqasm_testing(device: AwsDevice, run_kwargs: Dict[str, Any]): def openqasm_noisy_circuit_1qubit_noise_full_probability( - device: Device, run_kwargs: Dict[str, Any] + device: Device, run_kwargs: dict[str, Any] ): shots = run_kwargs["shots"] tol = get_tol(shots) @@ -675,7 +675,7 @@ def openqasm_noisy_circuit_1qubit_noise_full_probability( ) -def openqasm_result_types_bell_pair_testing(device: Device, run_kwargs: Dict[str, Any]): +def openqasm_result_types_bell_pair_testing(device: Device, run_kwargs: dict[str, Any]): openqasm_string = ( "OPENQASM 3;" "qubit[2] q;" diff --git a/test/integ_tests/job_test_script.py b/test/integ_tests/job_test_script.py index 42303465e..d2a74e6cc 100644 --- a/test/integ_tests/job_test_script.py +++ b/test/integ_tests/job_test_script.py @@ -43,7 +43,7 @@ def completed_job_script(): device = AwsDevice(get_job_device_arn()) bell = Circuit().h(0).cnot(0, 1) - for count in range(3): + for _ in range(3): task = device.run(bell, shots=10) print(task.result().measurement_counts) save_job_result({"converged": True, "energy": -0.2}) diff --git a/test/integ_tests/test_create_local_quantum_job.py b/test/integ_tests/test_create_local_quantum_job.py index 8ceae35cd..16c001f35 100644 --- a/test/integ_tests/test_create_local_quantum_job.py +++ b/test/integ_tests/test_create_local_quantum_job.py @@ -81,7 +81,7 @@ def test_completed_local_job(aws_session, capsys): }, ), ]: - with open(file_name, "r") as f: + with open(file_name) as f: assert json.loads(f.read()) == expected_data # Capture logs diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index 8b2eae758..be0e49a85 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -63,7 +63,7 @@ def test_failed_quantum_job(aws_session, capsys, failed_quantum_job): subdirectory = re.match( rf"s3://{s3_bucket}/jobs/{job.name}/(\d+)/script/source.tar.gz", job.metadata()["algorithmSpecification"]["scriptModeConfig"]["s3Uri"], - ).group(1) + )[1] keys = aws_session.list_keys( bucket=s3_bucket, prefix=f"jobs/{job_name}/{subdirectory}/", @@ -119,7 +119,7 @@ def test_completed_quantum_job(aws_session, capsys, completed_quantum_job): subdirectory = re.match( rf"s3://{s3_bucket}/jobs/{job.name}/(\d+)/script/source.tar.gz", job.metadata()["algorithmSpecification"]["scriptModeConfig"]["s3Uri"], - ).group(1) + )[1] keys = aws_session.list_keys( bucket=s3_bucket, prefix=f"jobs/{job_name}/{subdirectory}/", @@ -220,9 +220,9 @@ def __str__(self): input_data=str(Path("test", "integ_tests", "requirements")), ) def decorator_job(a, b: int, c=0, d: float = 1.0, **extras): - with open(Path(get_input_data_dir()) / "requirements.txt", "r") as f: + with open(Path(get_input_data_dir()) / "requirements.txt") as f: assert f.readlines() == ["pytest\n"] - with open(Path("test", "integ_tests", "requirements.txt"), "r") as f: + with open(Path("test", "integ_tests", "requirements.txt")) as f: assert f.readlines() == ["pytest\n"] assert dir(pytest) assert a.attribute == "value" @@ -232,7 +232,7 @@ def decorator_job(a, b: int, c=0, d: float = 1.0, **extras): assert extras["extra_arg"] == "extra_value" hp_file = os.environ["AMZN_BRAKET_HP_FILE"] - with open(hp_file, "r") as f: + with open(hp_file) as f: hyperparameters = json.load(f) assert hyperparameters == { "a": "MyClass{value}", @@ -255,7 +255,7 @@ def decorator_job(a, b: int, c=0, d: float = 1.0, **extras): os.chdir(temp_dir) try: job.download_result() - with open(Path(job.name, "test", "output_file.txt"), "r") as f: + with open(Path(job.name, "test", "output_file.txt")) as f: assert f.read() == "hello" assert ( Path(job.name, "results.json").exists() @@ -286,12 +286,12 @@ def test_decorator_job_submodule(): }, ) def decorator_job_submodule(): - with open(Path(get_input_data_dir("my_input")) / "requirements.txt", "r") as f: + with open(Path(get_input_data_dir("my_input")) / "requirements.txt") as f: assert f.readlines() == ["pytest\n"] - with open(Path("test", "integ_tests", "requirements.txt"), "r") as f: + with open(Path("test", "integ_tests", "requirements.txt")) as f: assert f.readlines() == ["pytest\n"] with open( - Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt", "r" + Path(get_input_data_dir("my_dir")) / "job_test_submodule" / "requirements.txt" ) as f: assert f.readlines() == ["pytest\n"] with open( @@ -302,7 +302,6 @@ def decorator_job_submodule(): "job_test_submodule", "requirements.txt", ), - "r", ) as f: assert f.readlines() == ["pytest\n"] assert dir(pytest) diff --git a/test/integ_tests/test_device_creation.py b/test/integ_tests/test_device_creation.py index 74b40afb6..540c09f61 100644 --- a/test/integ_tests/test_device_creation.py +++ b/test/integ_tests/test_device_creation.py @@ -11,7 +11,6 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import List, Set import pytest @@ -108,15 +107,14 @@ def _get_device_name(device: AwsDevice) -> str: return device_name -def _get_active_providers(aws_devices: List[AwsDevice]) -> Set[str]: - active_providers = set() - for device in aws_devices: - if device.status != "RETIRED": - active_providers.add(_get_provider_name(device)) +def _get_active_providers(aws_devices: list[AwsDevice]) -> set[str]: + active_providers = { + _get_provider_name(device) for device in aws_devices if device.status != "RETIRED" + } return active_providers -def _validate_device(device: AwsDevice, active_providers: Set[str]): +def _validate_device(device: AwsDevice, active_providers: set[str]): provider_name = _get_provider_name(device) if provider_name not in active_providers: provider_name = f"_{provider_name}" diff --git a/test/unit_tests/braket/ahs/test_atom_arrangement.py b/test/unit_tests/braket/ahs/test_atom_arrangement.py index 425458547..06a926163 100644 --- a/test/unit_tests/braket/ahs/test_atom_arrangement.py +++ b/test/unit_tests/braket/ahs/test_atom_arrangement.py @@ -52,9 +52,7 @@ def test_iteration(): atom_arrangement = AtomArrangement() for value in values: atom_arrangement.add(value) - returned_values = [] - for site in atom_arrangement: - returned_values.append(site.coordinate) + returned_values = [site.coordinate for site in atom_arrangement] assert values == returned_values diff --git a/test/unit_tests/braket/aws/common_test_utils.py b/test/unit_tests/braket/aws/common_test_utils.py index d9d6c3d44..aaca559f5 100644 --- a/test/unit_tests/braket/aws/common_test_utils.py +++ b/test/unit_tests/braket/aws/common_test_utils.py @@ -222,7 +222,7 @@ def run_and_assert( run_args.append(inputs) if gate_definitions is not None: run_args.append(gate_definitions) - run_args += extra_args if extra_args else [] + run_args += extra_args or [] run_kwargs = extra_kwargs or {} if reservation_arn: run_kwargs.update({"reservation_arn": reservation_arn}) @@ -295,7 +295,7 @@ def run_batch_and_assert( run_args.append(inputs) if gate_definitions is not None: run_args.append(gate_definitions) - run_args += extra_args if extra_args else [] + run_args += extra_args or [] run_kwargs = extra_kwargs or {} if reservation_arn: run_kwargs.update({"reservation_arn": reservation_arn}) @@ -350,7 +350,7 @@ def _create_task_args_and_kwargs( s3_folder if s3_folder is not None else default_s3_folder, shots if shots is not None else default_shots, ] - create_args += extra_args if extra_args else [] + create_args += extra_args or [] create_kwargs = extra_kwargs or {} create_kwargs.update( { diff --git a/test/unit_tests/braket/aws/test_aws_device.py b/test/unit_tests/braket/aws/test_aws_device.py index a85ca6eb9..7f9e6179d 100644 --- a/test/unit_tests/braket/aws/test_aws_device.py +++ b/test/unit_tests/braket/aws/test_aws_device.py @@ -833,7 +833,7 @@ def test_gate_calibration_refresh_no_url(arn): mock_session.region = RIGETTI_REGION device = AwsDevice(arn, mock_session) - assert device.refresh_gate_calibrations() == None + assert device.refresh_gate_calibrations() is None @patch("urllib.request.urlopen") @@ -945,7 +945,7 @@ def test_repr(arn): mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 mock_session.region = RIGETTI_REGION device = AwsDevice(arn, mock_session) - expected = "Device('name': {}, 'arn': {})".format(device.name, device.arn) + expected = f"Device('name': {device.name}, 'arn': {device.arn})" assert repr(device) == expected @@ -1882,7 +1882,8 @@ def test_get_devices_invalid_order_by(): @patch("braket.aws.aws_device.datetime") def test_get_device_availability(mock_utc_now): - class Expando(object): + + class Expando: pass class MockDevice(AwsDevice): @@ -1890,19 +1891,18 @@ def __init__(self, status, *execution_window_args): self._status = status self._properties = Expando() self._properties.service = Expando() - execution_windows = [] - for execution_day, window_start_hour, window_end_hour in execution_window_args: - execution_windows.append( - DeviceExecutionWindow.parse_raw( - json.dumps( - { - "executionDay": execution_day, - "windowStartHour": window_start_hour, - "windowEndHour": window_end_hour, - } - ) + execution_windows = [ + DeviceExecutionWindow.parse_raw( + json.dumps( + { + "executionDay": execution_day, + "windowStartHour": window_start_hour, + "windowEndHour": window_end_hour, + } ) ) + for execution_day, window_start_hour, window_end_hour in execution_window_args + ] self._properties.service.executionWindows = execution_windows test_sets = ( @@ -2041,7 +2041,7 @@ def test_device_topology_graph_data(get_device_data, expected_graph, arn): def test_device_no_href(): mock_session = Mock() mock_session.get_device.return_value = MOCK_GATE_MODEL_QPU_1 - device = AwsDevice(DWAVE_ARN, mock_session) + AwsDevice(DWAVE_ARN, mock_session) def test_parse_calibration_data(): diff --git a/test/unit_tests/braket/aws/test_aws_quantum_job.py b/test/unit_tests/braket/aws/test_aws_quantum_job.py index 8df2118cf..67ca98228 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_job.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_job.py @@ -364,7 +364,7 @@ def test_download_result_when_extract_path_not_provided( job_name = job_metadata["jobName"] quantum_job.download_result() - with open(f"{job_name}/results.json", "r") as file: + with open(f"{job_name}/results.json") as file: actual_data = json.loads(file.read())["dataDictionary"] assert expected_saved_data == actual_data @@ -382,7 +382,7 @@ def test_download_result_when_extract_path_provided( with tempfile.TemporaryDirectory() as temp_dir: quantum_job.download_result(temp_dir) - with open(f"{temp_dir}/{job_name}/results.json", "r") as file: + with open(f"{temp_dir}/{job_name}/results.json") as file: actual_data = json.loads(file.read())["dataDictionary"] assert expected_saved_data == actual_data diff --git a/test/unit_tests/braket/aws/test_aws_quantum_task.py b/test/unit_tests/braket/aws/test_aws_quantum_task.py index 6e789ec92..16a72da7a 100644 --- a/test/unit_tests/braket/aws/test_aws_quantum_task.py +++ b/test/unit_tests/braket/aws/test_aws_quantum_task.py @@ -172,7 +172,7 @@ def test_equality(arn, aws_session): def test_str(quantum_task): - expected = "AwsQuantumTask('id/taskArn':'{}')".format(quantum_task.id) + expected = f"AwsQuantumTask('id/taskArn':'{quantum_task.id}')" assert str(quantum_task) == expected @@ -1216,20 +1216,16 @@ def _assert_create_quantum_task_called_with( } if device_parameters is not None: - test_kwargs.update({"deviceParameters": device_parameters.json(exclude_none=True)}) + test_kwargs["deviceParameters"] = device_parameters.json(exclude_none=True) if tags is not None: - test_kwargs.update({"tags": tags}) + test_kwargs["tags"] = tags if reservation_arn: - test_kwargs.update( + test_kwargs["associations"] = [ { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", } - ) + ] aws_session.create_quantum_task.assert_called_with(**test_kwargs) diff --git a/test/unit_tests/braket/aws/test_aws_session.py b/test/unit_tests/braket/aws/test_aws_session.py index 56d23b2e9..fb0b309fb 100644 --- a/test/unit_tests/braket/aws/test_aws_session.py +++ b/test/unit_tests/braket/aws/test_aws_session.py @@ -410,7 +410,7 @@ def test_create_quantum_task_with_job_token(aws_session): } with patch.dict(os.environ, {"AMZN_BRAKET_JOB_TOKEN": job_token}): assert aws_session.create_quantum_task(**kwargs) == arn - kwargs.update({"jobToken": job_token}) + kwargs["jobToken"] = job_token aws_session.braket_client.create_quantum_task.assert_called_with(**kwargs) @@ -1284,10 +1284,10 @@ def test_describe_log_streams(aws_session, limit, next_token): } if limit: - describe_log_stream_args.update({"limit": limit}) + describe_log_stream_args["limit"] = limit if next_token: - describe_log_stream_args.update({"nextToken": next_token}) + describe_log_stream_args["nextToken"] = next_token aws_session.describe_log_streams(log_group, log_stream_prefix, limit, next_token) @@ -1314,7 +1314,7 @@ def test_get_log_events(aws_session, next_token): } if next_token: - log_events_args.update({"nextToken": next_token}) + log_events_args["nextToken"] = next_token aws_session.get_log_events(log_group, log_stream_name, start_time, start_from_head, next_token) diff --git a/test/unit_tests/braket/circuits/test_angled_gate.py b/test/unit_tests/braket/circuits/test_angled_gate.py index ae33d4029..4c5252c02 100644 --- a/test/unit_tests/braket/circuits/test_angled_gate.py +++ b/test/unit_tests/braket/circuits/test_angled_gate.py @@ -131,7 +131,7 @@ def test_np_float_angle_json(): angled_gate = AngledGate(angle=np.float32(0.15), qubit_count=1, ascii_symbols=["foo"]) angled_gate_json = BaseModel.construct(target=[0], angle=angled_gate.angle).json() match = re.match(r'\{"target": \[0], "angle": (\d*\.?\d*)}', angled_gate_json) - angle_value = float(match.group(1)) + angle_value = float(match[1]) assert angle_value == angled_gate.angle diff --git a/test/unit_tests/braket/circuits/test_circuit.py b/test/unit_tests/braket/circuits/test_circuit.py index c0c58ad35..71eecd1f1 100644 --- a/test/unit_tests/braket/circuits/test_circuit.py +++ b/test/unit_tests/braket/circuits/test_circuit.py @@ -245,9 +245,7 @@ def test_call_one_param_not_bound(): circ = Circuit().h(0).rx(angle=theta, target=1).ry(angle=alpha, target=0) new_circ = circ(theta=1) expected_circ = Circuit().h(0).rx(angle=1, target=1).ry(angle=alpha, target=0) - expected_parameters = set() - expected_parameters.add(alpha) - + expected_parameters = {alpha} assert new_circ == expected_circ and new_circ.parameters == expected_parameters @@ -3219,9 +3217,7 @@ def test_add_parameterized_check_true(): .ry(angle=theta, target=2) .ry(angle=theta, target=3) ) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3231,10 +3227,7 @@ def test_add_parameterized_instr_parameterized_circ_check_true(): alpha2 = FreeParameter("alpha") circ = Circuit().ry(angle=theta, target=0).ry(angle=alpha2, target=1).ry(angle=theta, target=2) circ.add_instruction(Instruction(Gate.Ry(alpha), 3)) - expected = set() - expected.add(theta) - expected.add(alpha) - + expected = {theta, alpha} assert circ.parameters == expected @@ -3242,9 +3235,7 @@ def test_add_non_parameterized_instr_parameterized_check_true(): theta = FreeParameter("theta") circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) circ.add_instruction(Instruction(Gate.Ry(0.1), 3)) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3252,9 +3243,7 @@ def test_add_circ_parameterized_check_true(): theta = FreeParameter("theta") circ = Circuit().ry(angle=1, target=0).add_circuit(Circuit().ry(angle=theta, target=0)) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3262,9 +3251,7 @@ def test_add_circ_not_parameterized_check_true(): theta = FreeParameter("theta") circ = Circuit().ry(angle=theta, target=0).add_circuit(Circuit().ry(angle=0.1, target=0)) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3285,9 +3272,7 @@ def test_parameterized_check_false(input_circ): def test_parameters(): theta = FreeParameter("theta") circ = Circuit().ry(angle=theta, target=0).ry(angle=theta, target=1).ry(angle=theta, target=2) - expected = set() - expected.add(theta) - + expected = {theta} assert circ.parameters == expected @@ -3345,9 +3330,7 @@ def test_make_bound_circuit_partial_bind(): expected_circ = ( Circuit().ry(angle=np.pi, target=0).ry(angle=np.pi, target=1).ry(angle=alpha, target=2) ) - expected_parameters = set() - expected_parameters.add(alpha) - + expected_parameters = {alpha} assert circ_new == expected_circ and circ_new.parameters == expected_parameters @@ -3552,7 +3535,7 @@ def test_parametrized_pulse_circuit(user_defined_frame): Circuit().rx(angle=theta, target=0).pulse_gate(pulse_sequence=pulse_sequence, targets=1) ) - assert circuit.parameters == set([frequency_parameter, length, theta]) + assert circuit.parameters == {frequency_parameter, length, theta} bound_half = circuit(theta=0.5, length=1e-5) assert bound_half.to_ir( diff --git a/test/unit_tests/braket/circuits/test_gates.py b/test/unit_tests/braket/circuits/test_gates.py index 3a7e621df..0b9ce52d7 100644 --- a/test/unit_tests/braket/circuits/test_gates.py +++ b/test/unit_tests/braket/circuits/test_gates.py @@ -250,21 +250,20 @@ def two_dimensional_matrix_valid_input(**kwargs): def create_valid_ir_input(irsubclasses): input = {} for subclass in irsubclasses: - input.update(valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")()) + input |= valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")() return input def create_valid_subroutine_input(irsubclasses, **kwargs): input = {} for subclass in irsubclasses: - input.update( - valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")(**kwargs) + input |= valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")( + **kwargs ) return input def create_valid_target_input(irsubclasses): - input = {} qubit_set = [] control_qubit_set = [] control_state = None @@ -285,11 +284,9 @@ def create_valid_target_input(irsubclasses): control_state = list(single_neg_control_valid_input()["control_state"]) elif subclass == DoubleControl: qubit_set = list(double_control_valid_ir_input().values()) + qubit_set - elif subclass in (Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): - pass - else: + elif subclass not in (Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): raise ValueError("Invalid subclass") - input["target"] = QubitSet(qubit_set) + input = {"target": QubitSet(qubit_set)} input["control"] = QubitSet(control_qubit_set) input["control_state"] = control_state return input @@ -327,9 +324,13 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif subclass in (NoTarget, Angle, TwoDimensionalMatrix, DoubleAngle, TripleAngle): - pass - else: + elif subclass not in ( + NoTarget, + Angle, + TwoDimensionalMatrix, + DoubleAngle, + TripleAngle, + ): raise ValueError("Invalid subclass") return qubit_count @@ -916,14 +917,13 @@ def test_gate_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwar ) if qubit_count == 1: multi_targets = [0, 1, 2] - instruction_list = [] - for target in multi_targets: - instruction_list.append( - Instruction( - operator=testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)), - target=target, - ) + instruction_list = [ + Instruction( + operator=testclass(**create_valid_gate_class_input(irsubclasses, **kwargs)), + target=target, ) + for target in multi_targets + ] subroutine = getattr(Circuit(), subroutine_name) subroutine_input = {"target": multi_targets} if Angle in irsubclasses: diff --git a/test/unit_tests/braket/circuits/test_instruction.py b/test/unit_tests/braket/circuits/test_instruction.py index 212e65949..1d04627e9 100644 --- a/test/unit_tests/braket/circuits/test_instruction.py +++ b/test/unit_tests/braket/circuits/test_instruction.py @@ -107,14 +107,11 @@ def test_adjoint_unsupported(): def test_str(instr): expected = ( - "Instruction('operator': {}, 'target': {}, " - "'control': {}, 'control_state': {}, 'power': {})" - ).format( - instr.operator, - instr.target, - instr.control, - instr.control_state.as_tuple, - instr.power, + f"Instruction('operator': {instr.operator}, " + f"'target': {instr.target}, " + f"'control': {instr.control}, " + f"'control_state': {instr.control_state.as_tuple}, " + f"'power': {instr.power})" ) assert str(instr) == expected diff --git a/test/unit_tests/braket/circuits/test_moments.py b/test/unit_tests/braket/circuits/test_moments.py index 982649f62..ed45b0aa3 100644 --- a/test/unit_tests/braket/circuits/test_moments.py +++ b/test/unit_tests/braket/circuits/test_moments.py @@ -153,7 +153,7 @@ def test_getitem(): def test_iter(moments): - assert [key for key in moments] == list(moments.keys()) + assert list(moments) == list(moments.keys()) def test_len(): diff --git a/test/unit_tests/braket/circuits/test_noises.py b/test/unit_tests/braket/circuits/test_noises.py index a9bd3f5a3..5fffba43f 100644 --- a/test/unit_tests/braket/circuits/test_noises.py +++ b/test/unit_tests/braket/circuits/test_noises.py @@ -232,21 +232,20 @@ def multi_probability_invalid_input(**kwargs): def create_valid_ir_input(irsubclasses): input = {} for subclass in irsubclasses: - input.update(valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")()) + input |= valid_ir_switcher.get(subclass.__name__, lambda: "Invalid subclass")() return input def create_valid_subroutine_input(irsubclasses, **kwargs): input = {} for subclass in irsubclasses: - input.update( - valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")(**kwargs) + input |= valid_subroutine_switcher.get(subclass.__name__, lambda: "Invalid subclass")( + **kwargs ) return input def create_valid_target_input(irsubclasses): - input = {} qubit_set = [] # based on the concept that control goes first in target input for subclass in irsubclasses: @@ -260,8 +259,8 @@ def create_valid_target_input(irsubclasses): qubit_set = list(single_control_valid_input().values()) + qubit_set elif subclass == DoubleControl: qubit_set = list(double_control_valid_ir_input().values()) + qubit_set - elif any( - subclass == i + elif all( + subclass != i for i in [ SingleProbability, SingleProbability_34, @@ -273,17 +272,15 @@ def create_valid_target_input(irsubclasses): MultiProbability, ] ): - pass - else: raise ValueError("Invalid subclass") - input["target"] = QubitSet(qubit_set) + input = {"target": QubitSet(qubit_set)} return input def create_valid_noise_class_input(irsubclasses, **kwargs): input = {} if SingleProbability in irsubclasses: - input.update(single_probability_valid_input()) + input |= single_probability_valid_input() if SingleProbability_34 in irsubclasses: input.update(single_probability_34_valid_input()) if SingleProbability_1516 in irsubclasses: @@ -320,8 +317,8 @@ def calculate_qubit_count(irsubclasses): qubit_count += 2 elif subclass == MultiTarget: qubit_count += 3 - elif any( - subclass == i + elif all( + subclass != i for i in [ SingleProbability, SingleProbability_34, @@ -333,8 +330,6 @@ def calculate_qubit_count(irsubclasses): TwoDimensionalMatrixList, ] ): - pass - else: raise ValueError("Invalid subclass") return qubit_count @@ -365,18 +360,17 @@ def test_noise_subroutine(testclass, subroutine_name, irclass, irsubclasses, kwa ) if qubit_count == 1: multi_targets = [0, 1, 2] - instruction_list = [] - for target in multi_targets: - instruction_list.append( - Instruction( - operator=testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)), - target=target, - ) + instruction_list = [ + Instruction( + operator=testclass(**create_valid_noise_class_input(irsubclasses, **kwargs)), + target=target, ) + for target in multi_targets + ] subroutine = getattr(Circuit(), subroutine_name) subroutine_input = {"target": multi_targets} if SingleProbability in irsubclasses: - subroutine_input.update(single_probability_valid_input()) + subroutine_input |= single_probability_valid_input() if SingleProbability_34 in irsubclasses: subroutine_input.update(single_probability_34_valid_input()) if SingleProbability_1516 in irsubclasses: diff --git a/test/unit_tests/braket/circuits/test_observable.py b/test/unit_tests/braket/circuits/test_observable.py index b7e9c201f..38689398a 100644 --- a/test/unit_tests/braket/circuits/test_observable.py +++ b/test/unit_tests/braket/circuits/test_observable.py @@ -130,7 +130,7 @@ def test_eigenvalue_not_implemented_by_default(observable): def test_str(observable): - expected = "{}('qubit_count': {})".format(observable.name, observable.qubit_count) + expected = f"{observable.name}('qubit_count': {observable.qubit_count})" assert str(observable) == expected assert observable.coefficient == 1 diff --git a/test/unit_tests/braket/circuits/test_observables.py b/test/unit_tests/braket/circuits/test_observables.py index 79f917af9..b6430d4d8 100644 --- a/test/unit_tests/braket/circuits/test_observables.py +++ b/test/unit_tests/braket/circuits/test_observables.py @@ -497,7 +497,7 @@ def test_flattened_tensor_product(): def test_hermitian_basis_rotation_gates(matrix, basis_rotation_matrix): expected_unitary = Gate.Unitary(matrix=basis_rotation_matrix) actual_rotation_gates = Observable.Hermitian(matrix=matrix).basis_rotation_gates - assert actual_rotation_gates == tuple([expected_unitary]) + assert actual_rotation_gates == (expected_unitary,) assert expected_unitary.matrix_equivalence(actual_rotation_gates[0]) @@ -596,16 +596,16 @@ def test_tensor_product_eigenvalues(observable, eigenvalues): @pytest.mark.parametrize( "observable,basis_rotation_gates", [ - (Observable.X() @ Observable.Y(), tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()])), + (Observable.X() @ Observable.Y(), (Gate.H(), Gate.Z(), Gate.S(), Gate.H())), ( Observable.X() @ Observable.Y() @ Observable.Z(), - tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()]), + (Gate.H(), Gate.Z(), Gate.S(), Gate.H()), ), ( Observable.X() @ Observable.Y() @ Observable.I(), - tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()]), + (Gate.H(), Gate.Z(), Gate.S(), Gate.H()), ), - (Observable.X() @ Observable.H(), tuple([Gate.H(), Gate.Ry(-np.pi / 4)])), + (Observable.X() @ Observable.H(), (Gate.H(), Gate.Ry(-np.pi / 4))), ], ) def test_tensor_product_basis_rotation_gates(observable, basis_rotation_gates): @@ -642,9 +642,7 @@ def test_sum_not_allowed_in_tensor_product(): @pytest.mark.parametrize( "observable,basis_rotation_gates", - [ - (Observable.X() + Observable.Y(), tuple([Gate.H(), Gate.Z(), Gate.S(), Gate.H()])), - ], + [(Observable.X() + Observable.Y(), (Gate.H(), Gate.Z(), Gate.S(), Gate.H()))], ) def test_no_basis_rotation_support_for_sum(observable, basis_rotation_gates): no_basis_rotation_support_for_sum = "Basis rotation calculation not supported for Sum" diff --git a/test/unit_tests/braket/circuits/test_quantum_operator.py b/test/unit_tests/braket/circuits/test_quantum_operator.py index ed58d5d63..3a26e8c82 100644 --- a/test/unit_tests/braket/circuits/test_quantum_operator.py +++ b/test/unit_tests/braket/circuits/test_quantum_operator.py @@ -138,5 +138,5 @@ def test_matrix_equivalence_non_quantum_operator(): def test_str(quantum_operator): - expected = "{}('qubit_count': {})".format(quantum_operator.name, quantum_operator.qubit_count) + expected = f"{quantum_operator.name}('qubit_count': {quantum_operator.qubit_count})" assert str(quantum_operator) == expected diff --git a/test/unit_tests/braket/devices/test_local_simulator.py b/test/unit_tests/braket/devices/test_local_simulator.py index f284b5a6f..a7e8bfe17 100644 --- a/test/unit_tests/braket/devices/test_local_simulator.py +++ b/test/unit_tests/braket/devices/test_local_simulator.py @@ -14,7 +14,7 @@ import json import textwrap import warnings -from typing import Any, Dict, Optional +from typing import Any, Optional from unittest.mock import Mock, patch import pytest @@ -116,10 +116,10 @@ def run( program: ir.jaqcd.Program, qubits: int, shots: Optional[int], - inputs: Optional[Dict[str, float]], + inputs: Optional[dict[str, float]], *args, **kwargs, - ) -> Dict[str, Any]: + ) -> dict[str, Any]: self._shots = shots self._qubits = qubits return GATE_MODEL_RESULT @@ -156,7 +156,7 @@ def properties(self) -> DeviceCapabilities: class DummyJaqcdSimulator(BraketSimulator): def run( self, program: ir.jaqcd.Program, qubits: int, shots: Optional[int], *args, **kwargs - ) -> Dict[str, Any]: + ) -> dict[str, Any]: if not isinstance(program, ir.jaqcd.Program): raise TypeError("Not a Jaqcd program") self._shots = shots @@ -253,7 +253,7 @@ def properties(self) -> DeviceCapabilities: class DummyProgramDensityMatrixSimulator(BraketSimulator): def run( self, program: ir.openqasm.Program, shots: Optional[int], *args, **kwargs - ) -> Dict[str, Any]: + ) -> dict[str, Any]: self._shots = shots return GATE_MODEL_RESULT diff --git a/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py b/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py index fdaff840b..247d1873b 100644 --- a/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py +++ b/test/unit_tests/braket/jobs/metrics_data/test_cwl_metrics_fetcher.py @@ -128,8 +128,6 @@ def test_get_metrics_timeout(mock_add_metrics, mock_get_metrics, aws_session): def get_log_events_forever(*args, **kwargs): - next_token = "1" token = kwargs.get("nextToken") - if token and token == "1": - next_token = "2" + next_token = "2" if token and token == "1" else "1" return {"events": EXAMPLE_METRICS_LOG_LINES, "nextForwardToken": next_token} diff --git a/test/unit_tests/braket/jobs/test_data_persistence.py b/test/unit_tests/braket/jobs/test_data_persistence.py index 6a5e27283..a4ac78f26 100644 --- a/test/unit_tests/braket/jobs/test_data_persistence.py +++ b/test/unit_tests/braket/jobs/test_data_persistence.py @@ -83,7 +83,7 @@ def test_save_job_checkpoint( if file_suffix else f"{tmp_dir}/{job_name}.json" ) - with open(expected_file_location, "r") as expected_file: + with open(expected_file_location) as expected_file: assert expected_file.read() == expected_saved_data @@ -267,7 +267,7 @@ def test_save_job_result(data_format, result_data, expected_saved_data): save_job_result(result_data, data_format) expected_file_location = f"{tmp_dir}/results.json" - with open(expected_file_location, "r") as expected_file: + with open(expected_file_location) as expected_file: assert expected_file.read() == expected_saved_data diff --git a/test/unit_tests/braket/jobs/test_hybrid_job.py b/test/unit_tests/braket/jobs/test_hybrid_job.py index 592af6840..b1739d879 100644 --- a/test/unit_tests/braket/jobs/test_hybrid_job.py +++ b/test/unit_tests/braket/jobs/test_hybrid_job.py @@ -511,7 +511,7 @@ def my_entry(*args, **kwargs): args, kwargs = (1, "two"), {"three": 3} template = _serialize_entry_point(my_entry, args, kwargs) - pickled_str = re.search(r"(?s)cloudpickle.loads\((.*?)\)\ndef my_entry", template).group(1) + pickled_str = re.search(r"(?s)cloudpickle.loads\((.*?)\)\ndef my_entry", template)[1] byte_str = ast.literal_eval(pickled_str) recovered = cloudpickle.loads(byte_str) diff --git a/test/unit_tests/braket/jobs/test_quantum_job_creation.py b/test/unit_tests/braket/jobs/test_quantum_job_creation.py index 8cd1fbca9..d12a29b00 100644 --- a/test/unit_tests/braket/jobs/test_quantum_job_creation.py +++ b/test/unit_tests/braket/jobs/test_quantum_job_creation.py @@ -148,9 +148,7 @@ def data_parallel(request): @pytest.fixture def distribution(data_parallel): - if data_parallel: - return "data_parallel" - return None + return "data_parallel" if data_parallel else None @pytest.fixture @@ -255,8 +253,8 @@ def create_job_args( reservation_arn, ): if request.param == "fixtures": - return dict( - (key, value) + return { + key: value for key, value in { "device": device, "source_module": source_module, @@ -277,7 +275,7 @@ def create_job_args( "reservation_arn": reservation_arn, }.items() if value is not None - ) + } elif request.param == "defaults": return { "device": device, @@ -339,7 +337,7 @@ def _translate_creation_args(create_job_args): "sagemaker_distributed_dataparallel_enabled": "true", "sagemaker_instance_type": instance_config.instanceType, } - hyperparameters.update(distributed_hyperparams) + hyperparameters |= distributed_hyperparams output_data_config = create_job_args["output_data_config"] or OutputDataConfig( s3Path=AwsSession.construct_s3_uri(default_bucket, "jobs", job_name, timestamp, "data") ) @@ -379,16 +377,12 @@ def _translate_creation_args(create_job_args): } if reservation_arn: - test_kwargs.update( + test_kwargs["associations"] = [ { - "associations": [ - { - "arn": reservation_arn, - "type": "RESERVATION_TIME_WINDOW_ARN", - } - ] + "arn": reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", } - ) + ] return test_kwargs diff --git a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py index c4199e81b..56f02aa12 100644 --- a/test/unit_tests/braket/pulse/ast/test_approximation_parser.py +++ b/test/unit_tests/braket/pulse/ast/test_approximation_parser.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from typing import List, Union +from typing import Union from unittest.mock import Mock import numpy as np @@ -85,7 +85,7 @@ def test_delay_multiple_frames(port): # Inst2 # Delay frame1 and frame2 by 10e-9 # frame2 is 0 from 0ns to 21ns - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 10e-9 expected_amplitudes["frame1"].put(shift_time_frame1, 0).put( @@ -104,7 +104,7 @@ def test_delay_multiple_frames(port): expected_phases["frame2"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -156,7 +156,7 @@ def test_delay_qubits(port): # Inst2 # Delay frame1 and frame2 by 10e-9 # frame2 is 0 from 0ns to 21ns - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 10e-9 expected_amplitudes["q0_frame"].put(shift_time_frame1, 0).put( @@ -177,7 +177,7 @@ def test_delay_qubits(port): expected_phases["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -230,7 +230,7 @@ def test_delay_no_args(port): # Inst2 # Delay frame1 and frame2 by 10e-9 # frame2 is 0 from 0ns to 21ns - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 10e-9 expected_amplitudes["q0_frame"].put(shift_time_frame1, 0).put( @@ -251,7 +251,7 @@ def test_delay_no_args(port): expected_phases["q0_q1_frame"].put(0, 0).put(shift_time_frame1 + pulse_length - port.dt, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -636,7 +636,7 @@ def test_play_drag_gaussian_waveforms(port): dtype=np.complex128, ) - shift_time = shift_time + 20e-9 + shift_time += 20e-9 for t, v in zip(times, values): expected_amplitudes["frame1"].put(t + shift_time, v) expected_frequencies["frame1"].put(t + shift_time, 1e8) @@ -681,7 +681,7 @@ def test_barrier_same_dt(port): expected_phases["frame2"].put(0, 0).put(11e-9, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -739,7 +739,7 @@ def test_barrier_no_args(port): expected_phases["frame2"].put(0, 0).put(11e-9, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -797,7 +797,7 @@ def test_barrier_qubits(port): expected_phases["q0_q1_frame"].put(0, 0).put(11e-9, 0) # Inst3 - shift_time_frame1 = shift_time_frame1 + pulse_length + shift_time_frame1 += pulse_length pulse_length = 16e-9 times = np.arange(0, pulse_length, port.dt) values = 1 * np.ones_like(times) @@ -1001,8 +1001,8 @@ def verify_results(results, expected_amplitudes, expected_frequencies, expected_ assert _all_close(results.phases[frame_id], expected_phases[frame_id], 1e-10) -def to_dict(frames: Union[Frame, List]): - if not isinstance(frames, List): +def to_dict(frames: Union[Frame, list]): + if not isinstance(frames, list): frames = [frames] frame_dict = dict() for frame in frames: diff --git a/test/unit_tests/braket/pulse/test_waveforms.py b/test/unit_tests/braket/pulse/test_waveforms.py index 0c56d3542..34f989c0b 100644 --- a/test/unit_tests/braket/pulse/test_waveforms.py +++ b/test/unit_tests/braket/pulse/test_waveforms.py @@ -33,10 +33,10 @@ ], ) def test_arbitrary_waveform(amps): - id = "arb_wf_x" - wf = ArbitraryWaveform(amps, id) + waveform_id = "arb_wf_x" + wf = ArbitraryWaveform(amps, waveform_id) assert wf.amplitudes == list(amps) - assert wf.id == id + assert wf.id == waveform_id oq_exp = wf._to_oqpy_expression() assert oq_exp.init_expression == list(amps) assert oq_exp.name == wf.id @@ -44,8 +44,8 @@ def test_arbitrary_waveform(amps): def test_arbitrary_waveform_repr(): amps = [1, 4, 5] - id = "arb_wf_x" - wf = ArbitraryWaveform(amps, id) + waveform_id = "arb_wf_x" + wf = ArbitraryWaveform(amps, waveform_id) expected = f"ArbitraryWaveform('id': {wf.id}, 'amplitudes': {wf.amplitudes})" assert repr(wf) == expected diff --git a/test/unit_tests/braket/registers/test_qubit.py b/test/unit_tests/braket/registers/test_qubit.py index 98f89cf8d..0c04a5d95 100644 --- a/test/unit_tests/braket/registers/test_qubit.py +++ b/test/unit_tests/braket/registers/test_qubit.py @@ -39,7 +39,7 @@ def test_index_gte_zero(qubit_index): def test_str(qubit): - expected = "Qubit({})".format(int(qubit)) + expected = f"Qubit({int(qubit)})" assert str(qubit) == expected diff --git a/test/unit_tests/braket/registers/test_qubit_set.py b/test/unit_tests/braket/registers/test_qubit_set.py index 1fd8d7212..1d730b967 100644 --- a/test/unit_tests/braket/registers/test_qubit_set.py +++ b/test/unit_tests/braket/registers/test_qubit_set.py @@ -31,20 +31,20 @@ def test_default_input(): def test_with_single(): - assert QubitSet(0) == tuple([Qubit(0)]) + assert QubitSet(0) == (Qubit(0),) def test_with_iterable(): - assert QubitSet([0, 1]) == tuple([Qubit(0), Qubit(1)]) + assert QubitSet([0, 1]) == (Qubit(0), Qubit(1)) def test_with_nested_iterable(): - assert QubitSet([0, 1, [2, 3]]) == tuple([Qubit(0), Qubit(1), Qubit(2), Qubit(3)]) + assert QubitSet([0, 1, [2, 3]]) == (Qubit(0), Qubit(1), Qubit(2), Qubit(3)) def test_with_qubit_set(): qubits = QubitSet([0, 1]) - assert QubitSet([qubits, [2, 3]]) == tuple([Qubit(0), Qubit(1), Qubit(2), Qubit(3)]) + assert QubitSet([qubits, [2, 3]]) == (Qubit(0), Qubit(1), Qubit(2), Qubit(3)) def test_flattening_does_not_recurse_infinitely(): diff --git a/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py b/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py index 29d21e58f..03f68b7cb 100644 --- a/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_annealing_quantum_task_result.py @@ -221,7 +221,7 @@ def test_data_sort_by_none(annealing_result, solutions, values, solution_counts) def test_data_selected_fields(annealing_result, solutions, values, solution_counts): d = list(annealing_result.data(selected_fields=["value"])) for i in range(len(solutions)): - assert d[i] == tuple([values[i]]) + assert d[i] == (values[i],) def test_data_reverse(annealing_result, solutions, values, solution_counts): diff --git a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py index 43dd06db7..5447bd8b2 100644 --- a/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py +++ b/test/unit_tests/braket/tasks/test_gate_model_quantum_task_result.py @@ -405,7 +405,7 @@ def test_from_string_measurement_probabilities(result_str_3): measurement_list = [list("011000") for _ in range(shots)] expected_measurements = np.asarray(measurement_list, dtype=int) assert np.allclose(task_result.measurements, expected_measurements) - assert task_result.measurement_counts == Counter(["011000" for x in range(shots)]) + assert task_result.measurement_counts == Counter(["011000" for _ in range(shots)]) assert not task_result.measurement_counts_copied_from_device assert task_result.measurement_probabilities_copied_from_device assert not task_result.measurements_copied_from_device diff --git a/test/unit_tests/braket/tasks/test_local_quantum_task.py b/test/unit_tests/braket/tasks/test_local_quantum_task.py index 6b583c608..aca0aa20d 100644 --- a/test/unit_tests/braket/tasks/test_local_quantum_task.py +++ b/test/unit_tests/braket/tasks/test_local_quantum_task.py @@ -57,5 +57,5 @@ def test_async(): def test_str(): - expected = "LocalQuantumTask('id':{})".format(TASK.id) + expected = f"LocalQuantumTask('id':{TASK.id})" assert str(TASK) == expected diff --git a/test/unit_tests/braket/timings/test_time_series.py b/test/unit_tests/braket/timings/test_time_series.py index c5a26334f..729813aa0 100755 --- a/test/unit_tests/braket/timings/test_time_series.py +++ b/test/unit_tests/braket/timings/test_time_series.py @@ -304,10 +304,10 @@ def test_discretize_values(default_time_series, value_res, expected_values): [ (TimeSeries(), TimeSeries(), True), (TimeSeries().put(0.1, 0.2), TimeSeries(), False), - (TimeSeries().put(float(0.1), float(0.2)), TimeSeries().put(float(0.1), float(0.2)), True), - (TimeSeries().put(float(1), float(0.2)), TimeSeries().put(int(1), float(0.2)), True), - (TimeSeries().put(float(0.1), float(0.2)), TimeSeries().put(float(0.2), float(0.2)), False), - (TimeSeries().put(float(0.1), float(0.3)), TimeSeries().put(float(0.1), float(0.2)), False), + (TimeSeries().put(0.1, 0.2), TimeSeries().put(0.1, 0.2), True), + (TimeSeries().put(float(1), 0.2), TimeSeries().put(1, 0.2), True), + (TimeSeries().put(0.1, 0.2), TimeSeries().put(0.2, 0.2), False), + (TimeSeries().put(0.1, 0.3), TimeSeries().put(0.1, 0.2), False), ], ) def test_all_close(first_series, second_series, expected_result): From c1a941fef3444d08b1d28eb69d0c9d3a3ae27972 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 18 Apr 2024 16:19:02 +0000 Subject: [PATCH 87/98] prepare release v1.78.0 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a91e3d8c..4cb8ae8a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.78.0 (2024-04-18) + +### Features + + * add phase RX gate + ## v1.77.6 (2024-04-17) ### Bug Fixes and Other Changes diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 07aeada44..d1dfdba08 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.77.7.dev0" +__version__ = "1.78.0" From 07d9e1e9408fb1f254b3f008d304194d991dd3d6 Mon Sep 17 00:00:00 2001 From: ci Date: Thu, 18 Apr 2024 16:19:02 +0000 Subject: [PATCH 88/98] update development version to v1.78.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index d1dfdba08..fec1b4f08 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.78.0" +__version__ = "1.78.1.dev0" From d9662227874f23d3b85622254e242dbba15569dc Mon Sep 17 00:00:00 2001 From: mbeach-aws <85963088+mbeach-aws@users.noreply.github.com> Date: Fri, 3 May 2024 13:37:46 -0400 Subject: [PATCH 89/98] feature: Direct Reservation context manager (#955) * feature: context manager for reservation arns Co-authored-by: Cody Wang --- examples/reservation.py | 17 +- src/braket/aws/__init__.py | 1 + src/braket/aws/aws_session.py | 27 +++ src/braket/aws/direct_reservations.py | 98 ++++++++++ test/integ_tests/test_reservation_arn.py | 24 +-- .../braket/aws/test_direct_reservations.py | 181 ++++++++++++++++++ 6 files changed, 333 insertions(+), 15 deletions(-) create mode 100644 src/braket/aws/direct_reservations.py create mode 100644 test/unit_tests/braket/aws/test_direct_reservations.py diff --git a/examples/reservation.py b/examples/reservation.py index 682f71f50..83be87ebd 100644 --- a/examples/reservation.py +++ b/examples/reservation.py @@ -11,7 +11,7 @@ # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. -from braket.aws import AwsDevice +from braket.aws import AwsDevice, DirectReservation from braket.circuits import Circuit from braket.devices import Devices @@ -19,6 +19,17 @@ device = AwsDevice(Devices.IonQ.Aria1) # To run a task in a device reservation, change the device to the one you reserved -# and fill in your reservation ARN -task = device.run(bell, shots=100, reservation_arn="reservation ARN") +# and fill in your reservation ARN. +with DirectReservation(device, reservation_arn=""): + task = device.run(bell, shots=100) +print(task.result().measurement_counts) + +# Alternatively, you may start the reservation globally +reservation = DirectReservation(device, reservation_arn="").start() +task = device.run(bell, shots=100) +print(task.result().measurement_counts) +reservation.stop() # stop creating tasks in the reservation + +# Lastly, you may pass the reservation ARN directly to a quantum task +task = device.run(bell, shots=100, reservation_arn="") print(task.result().measurement_counts) diff --git a/src/braket/aws/__init__.py b/src/braket/aws/__init__.py index d0b3a3411..3be348f34 100644 --- a/src/braket/aws/__init__.py +++ b/src/braket/aws/__init__.py @@ -16,3 +16,4 @@ from braket.aws.aws_quantum_task import AwsQuantumTask # noqa: F401 from braket.aws.aws_quantum_task_batch import AwsQuantumTaskBatch # noqa: F401 from braket.aws.aws_session import AwsSession # noqa: F401 +from braket.aws.direct_reservations import DirectReservation # noqa: F401 diff --git a/src/braket/aws/aws_session.py b/src/braket/aws/aws_session.py index 16a021e7d..b4cdfcd31 100644 --- a/src/braket/aws/aws_session.py +++ b/src/braket/aws/aws_session.py @@ -17,6 +17,7 @@ import os import os.path import re +import warnings from functools import cache from pathlib import Path from typing import Any, NamedTuple, Optional @@ -235,6 +236,32 @@ def create_quantum_task(self, **boto3_kwargs) -> str: Returns: str: The ARN of the quantum task. """ + # Add reservation arn if available and device is correct. + context_device_arn = os.getenv("AMZN_BRAKET_RESERVATION_DEVICE_ARN") + context_reservation_arn = os.getenv("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN") + + # if the task has a reservation_arn and also context does, raise a warning + # Raise warning if reservation ARN is found in both context and task parameters + task_has_reservation = any( + item.get("type") == "RESERVATION_TIME_WINDOW_ARN" + for item in boto3_kwargs.get("associations", []) + ) + if task_has_reservation and context_reservation_arn: + warnings.warn( + "A reservation ARN was passed to 'CreateQuantumTask', but it is being overridden " + "by a 'DirectReservation' context. If this was not intended, please review your " + "reservation ARN settings or the context in which 'CreateQuantumTask' is called." + ) + + # Ensure reservation only applies to specific device + if context_device_arn == boto3_kwargs["deviceArn"] and context_reservation_arn: + boto3_kwargs["associations"] = [ + { + "arn": context_reservation_arn, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + # Add job token to request, if available. job_token = os.getenv("AMZN_BRAKET_JOB_TOKEN") if job_token: diff --git a/src/braket/aws/direct_reservations.py b/src/braket/aws/direct_reservations.py new file mode 100644 index 000000000..4ffcb8fce --- /dev/null +++ b/src/braket/aws/direct_reservations.py @@ -0,0 +1,98 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from __future__ import annotations + +import os +import warnings +from contextlib import AbstractContextManager + +from braket.aws.aws_device import AwsDevice +from braket.devices import Device + + +class DirectReservation(AbstractContextManager): + """ + Context manager that modifies AwsQuantumTasks created within the context to use a reservation + ARN for all tasks targeting the specified device. Note: this context manager only allows for + one reservation at a time. + + Reservations are AWS account and device specific. Only the AWS account that created the + reservation can use your reservation ARN. Additionally, the reservation ARN is only valid on the + reserved device at the chosen start and end times. + + Args: + device (Device | str | None): The Braket device for which you have a reservation ARN, or + optionally the device ARN. + reservation_arn (str | None): The Braket Direct reservation ARN to be applied to all + quantum tasks run within the context. + + Examples: + As a context manager + >>> with DirectReservation(device_arn, reservation_arn=""): + ... task1 = device.run(circuit, shots) + ... task2 = device.run(circuit, shots) + + or start the reservation + >>> DirectReservation(device_arn, reservation_arn="").start() + ... task1 = device.run(circuit, shots) + ... task2 = device.run(circuit, shots) + + References: + + [1] https://docs.aws.amazon.com/braket/latest/developerguide/braket-reservations.html + """ + + _is_active = False # Class variable to track active reservation context + + def __init__(self, device: Device | str | None, reservation_arn: str | None): + if isinstance(device, AwsDevice): + self.device_arn = device.arn + elif isinstance(device, str): + self.device_arn = AwsDevice(device).arn # validate ARN early + elif isinstance(device, Device) or device is None: # LocalSimulator + warnings.warn( + "Using a local simulator with the reservation. For a reservation on a QPU, please " + "ensure the device matches the reserved Braket device." + ) + self.device_arn = "" # instead of None, use empty string + else: + raise TypeError("Device must be an AwsDevice or its ARN, or a local simulator device.") + + self.reservation_arn = reservation_arn + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self.stop() + + def start(self) -> None: + """Start the reservation context.""" + if DirectReservation._is_active: + raise RuntimeError("Another reservation is already active.") + + os.environ["AMZN_BRAKET_RESERVATION_DEVICE_ARN"] = self.device_arn + if self.reservation_arn: + os.environ["AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN"] = self.reservation_arn + DirectReservation._is_active = True + + def stop(self) -> None: + """Stop the reservation context.""" + if not DirectReservation._is_active: + warnings.warn("Reservation context is not active.") + return + os.environ.pop("AMZN_BRAKET_RESERVATION_DEVICE_ARN", None) + os.environ.pop("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN", None) + DirectReservation._is_active = False diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py index 98b87f075..f5efd7227 100644 --- a/test/integ_tests/test_reservation_arn.py +++ b/test/integ_tests/test_reservation_arn.py @@ -15,12 +15,12 @@ import pytest from botocore.exceptions import ClientError -from test_create_quantum_job import decorator_python_version -from braket.aws import AwsDevice +from braket.aws import AwsDevice, DirectReservation from braket.circuits import Circuit from braket.devices import Devices from braket.jobs import get_job_device_arn, hybrid_job +from braket.test.integ_tests.test_create_quantum_job import decorator_python_version @pytest.fixture @@ -36,11 +36,11 @@ def test_create_task_via_invalid_reservation_arn_on_qpu(reservation_arn): device = AwsDevice(Devices.IonQ.Harmony) with pytest.raises(ClientError, match="Reservation arn is invalid"): - device.run( - circuit, - shots=10, - reservation_arn=reservation_arn, - ) + device.run(circuit, shots=10, reservation_arn=reservation_arn) + + with pytest.raises(ClientError, match="Reservation arn is invalid"): + with DirectReservation(device, reservation_arn=reservation_arn): + device.run(circuit, shots=10) def test_create_task_via_reservation_arn_on_simulator(reservation_arn): @@ -48,11 +48,11 @@ def test_create_task_via_reservation_arn_on_simulator(reservation_arn): device = AwsDevice(Devices.Amazon.SV1) with pytest.raises(ClientError, match="Braket Direct is not supported for"): - device.run( - circuit, - shots=10, - reservation_arn=reservation_arn, - ) + device.run(circuit, shots=10, reservation_arn=reservation_arn) + + with pytest.raises(ClientError, match="Braket Direct is not supported for"): + with DirectReservation(device, reservation_arn=reservation_arn): + device.run(circuit, shots=10) @pytest.mark.xfail( diff --git a/test/unit_tests/braket/aws/test_direct_reservations.py b/test/unit_tests/braket/aws/test_direct_reservations.py new file mode 100644 index 000000000..332421e7a --- /dev/null +++ b/test/unit_tests/braket/aws/test_direct_reservations.py @@ -0,0 +1,181 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +import os +from unittest.mock import MagicMock, patch + +import pytest + +from braket.aws import AwsDevice, AwsSession, DirectReservation +from braket.devices import LocalSimulator + +RESERVATION_ARN = "arn:aws:braket:us-east-1:123456789:reservation/uuid" +DEVICE_ARN = "arn:aws:braket:us-east-1:123456789:device/qpu/ionq/Forte-1" +VALUE_ERROR_MESSAGE = "Device must be an AwsDevice or its ARN, or a local simulator device." +RUNTIME_ERROR_MESSAGE = "Another reservation is already active." + + +@pytest.fixture +def aws_device(): + mock_device = MagicMock(spec=AwsDevice) + mock_device._arn = DEVICE_ARN + type(mock_device).arn = property(lambda x: DEVICE_ARN) + return mock_device + + +def test_direct_reservation_aws_device(aws_device): + with DirectReservation(aws_device, RESERVATION_ARN) as reservation: + assert reservation.device_arn == DEVICE_ARN + assert reservation.reservation_arn == RESERVATION_ARN + assert reservation._is_active + + +def test_direct_reservation_device_str(aws_device): + with patch( + "braket.aws.AwsDevice.__init__", + side_effect=lambda self, *args, **kwargs: setattr(self, "_arn", DEVICE_ARN), + autospec=True, + ): + with patch("braket.aws.AwsDevice", return_value=aws_device, autospec=True): + with DirectReservation(DEVICE_ARN, RESERVATION_ARN) as reservation: + assert reservation.device_arn == DEVICE_ARN + assert reservation.reservation_arn == RESERVATION_ARN + assert reservation._is_active + + +def test_direct_reservation_local_simulator(): + mock_device = MagicMock(spec=LocalSimulator) + with pytest.warns(UserWarning): + with DirectReservation(mock_device, RESERVATION_ARN) as reservation: + assert os.environ["AMZN_BRAKET_RESERVATION_DEVICE_ARN"] == "" + assert os.environ["AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN"] == RESERVATION_ARN + assert reservation._is_active is True + + +@pytest.mark.parametrize("device", [123, False, [aws_device], {"a": 1}]) +def test_direct_reservation_invalid_inputs(device): + with pytest.raises(TypeError): + DirectReservation(device, RESERVATION_ARN) + + +def test_direct_reservation_local_no_reservation(): + mock_device = MagicMock(spec=LocalSimulator) + mock_device.create_quantum_task = MagicMock() + kwargs = { + "program": {"ir": '{"instructions":[]}', "qubitCount": 4}, + "shots": 1, + } + with DirectReservation(mock_device, None): + mock_device.create_quantum_task(**kwargs) + mock_device.create_quantum_task.assert_called_once_with(**kwargs) + + +def test_context_management(aws_device): + with DirectReservation(aws_device, RESERVATION_ARN): + assert os.getenv("AMZN_BRAKET_RESERVATION_DEVICE_ARN") == DEVICE_ARN + assert os.getenv("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN") == RESERVATION_ARN + assert not os.getenv("AMZN_BRAKET_RESERVATION_DEVICE_ARN") + assert not os.getenv("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN") + + +def test_start_reservation_already_active(aws_device): + reservation = DirectReservation(aws_device, RESERVATION_ARN) + reservation.start() + with pytest.raises(RuntimeError, match=RUNTIME_ERROR_MESSAGE): + reservation.start() + reservation.stop() + + +def test_stop_reservation_not_active(aws_device): + reservation = DirectReservation(aws_device, RESERVATION_ARN) + with pytest.warns(UserWarning): + reservation.stop() + + +def test_multiple_start_stop_cycles(aws_device): + reservation = DirectReservation(aws_device, RESERVATION_ARN) + reservation.start() + reservation.stop() + reservation.start() + reservation.stop() + assert not os.getenv("AMZN_BRAKET_RESERVATION_DEVICE_ARN") + assert not os.getenv("AMZN_BRAKET_RESERVATION_TIME_WINDOW_ARN") + + +def test_two_direct_reservations(aws_device): + with pytest.raises(RuntimeError, match=RUNTIME_ERROR_MESSAGE): + with DirectReservation(aws_device, RESERVATION_ARN): + with DirectReservation(aws_device, "reservation_arn_example_2"): + pass + + +def test_create_quantum_task_with_correct_device_and_reservation(aws_device): + kwargs = {"deviceArn": DEVICE_ARN, "shots": 1} + with patch("boto3.client"): + mock_client = MagicMock() + aws_session = AwsSession(braket_client=mock_client) + with DirectReservation(aws_device, RESERVATION_ARN): + aws_session.create_quantum_task(**kwargs) + kwargs["associations"] = [ + { + "arn": RESERVATION_ARN, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ] + mock_client.create_quantum_task.assert_called_once_with(**kwargs) + + +def test_warning_for_overridden_reservation_arn(aws_device): + kwargs = { + "deviceArn": DEVICE_ARN, + "shots": 1, + "associations": [ + { + "arn": "task_reservation_arn", + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ], + } + correct_kwargs = { + "deviceArn": DEVICE_ARN, + "shots": 1, + "associations": [ + { + "arn": RESERVATION_ARN, + "type": "RESERVATION_TIME_WINDOW_ARN", + } + ], + } + with patch("boto3.client"): + mock_client = MagicMock() + aws_session = AwsSession(braket_client=mock_client) + with pytest.warns( + UserWarning, + match="A reservation ARN was passed to 'CreateQuantumTask', but it is being overridden", + ): + with DirectReservation(aws_device, RESERVATION_ARN): + aws_session.create_quantum_task(**kwargs) + mock_client.create_quantum_task.assert_called_once_with(**correct_kwargs) + + +def test_warning_not_triggered_wrong_association_type(): + kwargs = { + "deviceArn": DEVICE_ARN, + "shots": 1, + "associations": [{"type": "OTHER_TYPE"}], + } + with patch("boto3.client"): + mock_client = MagicMock() + aws_session = AwsSession(braket_client=mock_client) + aws_session.create_quantum_task(**kwargs) + mock_client.create_quantum_task.assert_called_once_with(**kwargs) From 84c3bb7c7cecefbe0676c92c1748aa729bdfc6cf Mon Sep 17 00:00:00 2001 From: Ashlyn Hanson <65787294+ashlhans@users.noreply.github.com> Date: Fri, 3 May 2024 14:44:14 -0700 Subject: [PATCH 90/98] doc: correct the example in the measure docstring (#965) --- src/braket/circuits/circuit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 94d53248c..f15e03647 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -737,7 +737,6 @@ def measure(self, target_qubits: QubitSetInput) -> Circuit: [Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)]), Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), Qubit(1)]), - Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(2)]), Instruction('operator': Measure, 'target': QubitSet([Qubit(0)])] """ if not isinstance(target_qubits, Iterable): From 2730aa1405695d389e747815a20ef2ee37d39389 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 6 May 2024 14:23:45 -0700 Subject: [PATCH 91/98] infra: add opts for tox builds ran in parallel (#759) --- .github/workflows/check-format.yml | 2 +- README.md | 6 ++++++ tox.ini | 3 +-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index 8f6807b5e..a6106b2d7 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -26,4 +26,4 @@ jobs: pip install tox - name: Run code format checks run: | - tox -e linters_check + tox -e linters_check -p auto diff --git a/README.md b/README.md index b03bd1ef4..0c935853d 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,12 @@ To run linters and doc generators and unit tests: tox ``` +or if your machine can handle multithreaded workloads, run them in parallel with: + +```bash +tox -p auto +``` + ### Integration Tests First, configure a profile to use your account to interact with AWS. To learn more, see [Configure AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html). diff --git a/tox.ini b/tox.ini index 98a9b30e3..d4fae64c5 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,6 @@ [tox] envlist = clean,linters,docs,unit-tests - [testenv] parallel_show_output = true package = wheel @@ -111,7 +110,7 @@ deps = sphinx-rtd-theme sphinxcontrib-apidoc commands = - sphinx-build -E -T -b html doc build/documentation/html + sphinx-build -E -T -b html doc build/documentation/html -j auto [testenv:serve-docs] basepython = python3 From 7824bfbb4ed7215143d578f1754bc5881458cf19 Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Mon, 6 May 2024 15:04:00 -0700 Subject: [PATCH 92/98] infra: allow worksteal for testing (#960) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index d75c4f034..ab93f5955 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,7 +5,7 @@ test=pytest xfail_strict = true # https://pytest-xdist.readthedocs.io/en/latest/known-limitations.html addopts = - --verbose -n logical --durations=0 --durations-min=1 + --verbose -n logical --durations=0 --durations-min=1 --dist worksteal testpaths = test/unit_tests filterwarnings= # Issue #557 in `pytest-cov` (currently v4.x) has not moved for a while now, From 6d30734ace5d5b98884626aba627cd421bb4a647 Mon Sep 17 00:00:00 2001 From: Cody Wang Date: Mon, 6 May 2024 15:20:42 -0700 Subject: [PATCH 93/98] test: Extract `decorator_python_version` (#968) --- model.tar.gz | Bin 336 -> 0 bytes test/integ_tests/job_testing_utils.py | 25 ++++++++++++++++++++ test/integ_tests/test_create_quantum_job.py | 12 ++-------- test/integ_tests/test_reservation_arn.py | 2 +- 4 files changed, 28 insertions(+), 11 deletions(-) delete mode 100644 model.tar.gz create mode 100644 test/integ_tests/job_testing_utils.py diff --git a/model.tar.gz b/model.tar.gz deleted file mode 100644 index 93bf6a4a03f7d08314601e2907a704651eb0b07a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 336 zcmV-W0k8faiwFP!000001MHQ}OT#c2#(Va!2svx^rcE0sc<_@AJcxpL8(AB)b4^B) z%4F<+H{HjjuuWKXsQq1x%3_`*Q35O_NgDPfvx=n;VBX>FXTDp6uL>oc|;iH ztQ*0R_tGt1vB_fzy6azFJY4nqPd6kr(*HrLP1T3Kf&b071ir@3{KvGOe~7{W?VZW5 zu+G2H+HI@b<^R(B&+yQQH|ZYJS6PVVLx9iF3@cGcKUmphq=$Bp2`9+JzZAK3G8=ep zA>m_$-z!zCY6Zn}FI2{Lo>s{h=3~(^)ykK>$jr~2DW$KH$_tfy0wi27yVa%;u4*+I ii(EN5b$EX0i)v|UY58M(0ssL2{{sNvw;Ch>3;+NyA)$Q$ diff --git a/test/integ_tests/job_testing_utils.py b/test/integ_tests/job_testing_utils.py new file mode 100644 index 000000000..4493df180 --- /dev/null +++ b/test/integ_tests/job_testing_utils.py @@ -0,0 +1,25 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +import re + +from braket.aws import AwsSession +from braket.jobs import Framework, retrieve_image + + +def decorator_python_version(): + aws_session = AwsSession() + image_uri = retrieve_image(Framework.BASE, aws_session.region) + tag = aws_session.get_full_image_tag(image_uri) + major_version, minor_version = re.search(r"-py(\d)(\d+)-", tag).groups() + return int(major_version), int(minor_version) diff --git a/test/integ_tests/test_create_quantum_job.py b/test/integ_tests/test_create_quantum_job.py index be0e49a85..ce88d122b 100644 --- a/test/integ_tests/test_create_quantum_job.py +++ b/test/integ_tests/test_create_quantum_job.py @@ -22,19 +22,11 @@ import job_test_script import pytest from job_test_module.job_test_submodule.job_test_submodule_file import submodule_helper +from job_testing_utils import decorator_python_version -from braket.aws import AwsSession from braket.aws.aws_quantum_job import AwsQuantumJob from braket.devices import Devices -from braket.jobs import Framework, get_input_data_dir, hybrid_job, retrieve_image, save_job_result - - -def decorator_python_version(): - aws_session = AwsSession() - image_uri = retrieve_image(Framework.BASE, aws_session.region) - tag = aws_session.get_full_image_tag(image_uri) - major_version, minor_version = re.search(r"-py(\d)(\d+)-", tag).groups() - return int(major_version), int(minor_version) +from braket.jobs import get_input_data_dir, hybrid_job, save_job_result def test_failed_quantum_job(aws_session, capsys, failed_quantum_job): diff --git a/test/integ_tests/test_reservation_arn.py b/test/integ_tests/test_reservation_arn.py index f5efd7227..64135f76e 100644 --- a/test/integ_tests/test_reservation_arn.py +++ b/test/integ_tests/test_reservation_arn.py @@ -15,12 +15,12 @@ import pytest from botocore.exceptions import ClientError +from job_testing_utils import decorator_python_version from braket.aws import AwsDevice, DirectReservation from braket.circuits import Circuit from braket.devices import Devices from braket.jobs import get_job_device_arn, hybrid_job -from braket.test.integ_tests.test_create_quantum_job import decorator_python_version @pytest.fixture From 376fb90d122ba88d2b312081705a8d1185903a95 Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 6 May 2024 22:40:25 +0000 Subject: [PATCH 94/98] prepare release v1.79.0 --- CHANGELOG.md | 10 ++++++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cb8ae8a6..49eba0f5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v1.79.0 (2024-05-06) + +### Features + + * Direct Reservation context manager + +### Documentation Changes + + * correct the example in the measure docstring + ## v1.78.0 (2024-04-18) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index fec1b4f08..1d9dd72b1 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.78.1.dev0" +__version__ = "1.79.0" From b791858ca6a5e036d727a9fb4e0f377b0b0b4bff Mon Sep 17 00:00:00 2001 From: ci Date: Mon, 6 May 2024 22:40:25 +0000 Subject: [PATCH 95/98] update development version to v1.79.1.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1d9dd72b1..1f46f945a 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.79.0" +__version__ = "1.79.1.dev0" From 6c4282e7eafcff906c4cfc6804bf06209d82a4fc Mon Sep 17 00:00:00 2001 From: Abe Coull <85974725+math411@users.noreply.github.com> Date: Tue, 7 May 2024 18:09:55 -0700 Subject: [PATCH 96/98] fix: check the qubit set length against observables (#970) --- src/braket/circuits/result_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/circuits/result_type.py b/src/braket/circuits/result_type.py index b66d4da67..3e9f0dfad 100644 --- a/src/braket/circuits/result_type.py +++ b/src/braket/circuits/result_type.py @@ -221,7 +221,7 @@ def __init__( "target length is equal to the observable term's qubits count." ) self._target = [QubitSet(term_target) for term_target in target] - for term_target, obs in zip(target, observable.summands): + for term_target, obs in zip(self._target, observable.summands): if obs.qubit_count != len(term_target): raise ValueError( "Sum observable's target shape must be a nested list where each term's " From dfd75b38aba98ba748ef7c99d8e241f527760c39 Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 8 May 2024 22:01:49 +0000 Subject: [PATCH 97/98] prepare release v1.79.1 --- CHANGELOG.md | 6 ++++++ src/braket/_sdk/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49eba0f5b..e54b3f658 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v1.79.1 (2024-05-08) + +### Bug Fixes and Other Changes + + * check the qubit set length against observables + ## v1.79.0 (2024-05-06) ### Features diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index 1f46f945a..c15ebeb09 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.79.1.dev0" +__version__ = "1.79.1" From ec5edafd43dbb498bf5e0dc623f6b1ff74b4698c Mon Sep 17 00:00:00 2001 From: ci Date: Wed, 8 May 2024 22:01:49 +0000 Subject: [PATCH 98/98] update development version to v1.79.2.dev0 --- src/braket/_sdk/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/braket/_sdk/_version.py b/src/braket/_sdk/_version.py index c15ebeb09..0215a4833 100644 --- a/src/braket/_sdk/_version.py +++ b/src/braket/_sdk/_version.py @@ -15,4 +15,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "1.79.1" +__version__ = "1.79.2.dev0"