diff --git a/build.py b/build.py index 25c8d25b88..dae48f7b7a 100755 --- a/build.py +++ b/build.py @@ -133,6 +133,7 @@ def step_end(): if os.getenv("GITHUB_ACTIONS") == "true": print(f"::endgroup::") + def use_python_env(folder): # Check if in a virtual environment if ( @@ -323,8 +324,14 @@ def use_python_env(folder): if build_samples: step_start("Building qsharp samples") - project_directories = [dir for dir in os.walk(samples_src) if "qsharp.json" in dir[2]] - single_file_directories = [candidate for candidate in os.walk(samples_src) if all([not proj_dir[0] in candidate[0] for proj_dir in project_directories])] + project_directories = [ + dir for dir in os.walk(samples_src) if "qsharp.json" in dir[2] + ] + single_file_directories = [ + candidate + for candidate in os.walk(samples_src) + if all([not proj_dir[0] in candidate[0] for proj_dir in project_directories]) + ] files = [ os.path.join(dp, f) @@ -332,7 +339,7 @@ def use_python_env(folder): for f in filenames if os.path.splitext(f)[1] == ".qs" ] - projects = [ + projects = [ os.path.join(dp, f) for dp, _, filenames in project_directories for f in filenames @@ -344,7 +351,12 @@ def use_python_env(folder): for file in files: subprocess.run((cargo_args + ["--", file]), check=True, text=True, cwd=root_dir) for project in projects: - subprocess.run((cargo_args + ["--", "--qsharp-json", project]), check=True, text=True, cwd=root_dir) + subprocess.run( + (cargo_args + ["--", "--qsharp-json", project]), + check=True, + text=True, + cwd=root_dir, + ) step_end() if build_npm: @@ -414,8 +426,13 @@ def use_python_env(folder): step_start("Running notebook samples integration tests") # Find all notebooks in the samples directory. Skip the basic sample and the azure submission sample, since those won't run # nicely in automation. - notebook_files = [os.path.join(dp, f) for dp, _, filenames in os.walk(samples_src) for f in filenames if f.endswith(".ipynb") - and not (f.startswith("sample.") or f.startswith("azure_submission."))] + notebook_files = [ + os.path.join(dp, f) + for dp, _, filenames in os.walk(samples_src) + for f in filenames + if f.endswith(".ipynb") + and not (f.startswith("sample.") or f.startswith("azure_submission.")) + ] python_bin = use_python_env(samples_src) # copy the process env vars @@ -442,7 +459,9 @@ def use_python_env(folder): "install", widgets_src, ] - subprocess.run(pip_install_args, check=True, text=True, cwd=widgets_src, env=pip_env) + subprocess.run( + pip_install_args, check=True, text=True, cwd=widgets_src, env=pip_env + ) # Install other dependencies pip_install_args = [ @@ -459,17 +478,27 @@ def use_python_env(folder): for notebook in notebook_files: print(f"Running {notebook}") # Run the notebook process, capturing stdout and only displaying it if there is an error - result = subprocess.run([python_bin, - "-m", - "nbconvert", - "--to", - "notebook", - "--stdout", - "--ExecutePreprocessor.timeout=60", - "--sanitize-html", - "--execute", - notebook], - check=False, text=True, cwd=root_dir, env=pip_env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding="utf-8") + result = subprocess.run( + [ + python_bin, + "-m", + "nbconvert", + "--to", + "notebook", + "--stdout", + "--ExecutePreprocessor.timeout=60", + "--sanitize-html", + "--execute", + notebook, + ], + check=False, + text=True, + cwd=root_dir, + env=pip_env, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + encoding="utf-8", + ) if result.returncode != 0: print(result.stdout) raise Exception(f"Error running {notebook}") diff --git a/jupyterlab/qsharp-jupyterlab/__init__.py b/jupyterlab/qsharp-jupyterlab/__init__.py index 2fb44c70a3..39f2892d8e 100644 --- a/jupyterlab/qsharp-jupyterlab/__init__.py +++ b/jupyterlab/qsharp-jupyterlab/__init__.py @@ -2,8 +2,4 @@ def _jupyter_labextension_paths(): - return [{ - "src": "labextension", - "dest": "qsharp-jupyterlab" - }] - + return [{"src": "labextension", "dest": "qsharp-jupyterlab"}] diff --git a/jupyterlab/setup.py b/jupyterlab/setup.py index bea2337431..aefdf20dbc 100644 --- a/jupyterlab/setup.py +++ b/jupyterlab/setup.py @@ -1 +1 @@ -__import__('setuptools').setup() +__import__("setuptools").setup() diff --git a/pip/qsharp/_fs.py b/pip/qsharp/_fs.py index dfc91dbe47..2b84419bda 100644 --- a/pip/qsharp/_fs.py +++ b/pip/qsharp/_fs.py @@ -15,11 +15,11 @@ def map_dir(e: str) -> Dict[str, str]: return { "path": os.path.join(dir_path, e), "entry_name": e, - "type": "file" - if os.path.isfile(os.path.join(dir_path, e)) - else "folder" - if os.path.isdir(os.path.join(dir_path, e)) - else "unknown", + "type": ( + "file" + if os.path.isfile(os.path.join(dir_path, e)) + else "folder" if os.path.isdir(os.path.join(dir_path, e)) else "unknown" + ), } return list(map(map_dir, os.listdir(dir_path))) diff --git a/pip/qsharp/_native.pyi b/pip/qsharp/_native.pyi index f73a09e1c5..ce323bf50b 100644 --- a/pip/qsharp/_native.pyi +++ b/pip/qsharp/_native.pyi @@ -45,6 +45,7 @@ class Interpreter: :param list_directory: A function that lists the contents of a directory. """ ... + def interpret(self, input: str, output_fn: Callable[[Output], None]) -> Any: """ Interprets Q# source code. @@ -57,9 +58,8 @@ class Interpreter: :raises QSharpError: If there is an error interpreting the input. """ ... - def run( - self, entry_expr: str, output_fn: Callable[[Output], None] - ) -> Any: + + def run(self, entry_expr: str, output_fn: Callable[[Output], None]) -> Any: """ Runs the given Q# expression with an independent instance of the simulator. @@ -71,6 +71,7 @@ class Interpreter: :raises QSharpError: If there is an error interpreting the input. """ ... + def qir(self, entry_expr: str) -> str: """ Generates QIR from Q# source code. @@ -80,6 +81,7 @@ class Interpreter: :returns qir: The QIR string. """ ... + def estimate(self, entry_expr: str, params: str) -> str: """ Estimates resources for Q# source code. @@ -90,6 +92,7 @@ class Interpreter: :returns resources: The estimated resources. """ ... + def set_quantum_seed(self, seed: Optional[int]) -> None: """ Sets the seed for the quantum random number generator. @@ -98,6 +101,7 @@ class Interpreter: the seed will be generated from entropy. """ ... + def set_classical_seed(self, seed: Optional[int]) -> None: """ Sets the seed for the classical random number generator. @@ -106,6 +110,7 @@ class Interpreter: the seed will be generated from entropy. """ ... + def dump_machine(self) -> StateDumpData: """ Returns the sparse state vector of the simulator as a StateDump object. diff --git a/pip/qsharp/_qsharp.py b/pip/qsharp/_qsharp.py index 04aa933206..9c88cd6679 100644 --- a/pip/qsharp/_qsharp.py +++ b/pip/qsharp/_qsharp.py @@ -86,10 +86,16 @@ def init( # if no features were passed in as an argument, use the features from the manifest. # this way we prefer the features from the argument over those from the manifest. if language_features == [] and manifest_descriptor != None: - language_features = manifest_descriptor["manifest"].get("languageFeatures") or [] + language_features = ( + manifest_descriptor["manifest"].get("languageFeatures") or [] + ) _interpreter = Interpreter( - target_profile, language_features, manifest_descriptor, read_file, list_directory + target_profile, + language_features, + manifest_descriptor, + read_file, + list_directory, ) # Return the configuration information to provide a hint to the @@ -252,6 +258,7 @@ def estimate( json.loads(get_interpreter().estimate(entry_expr, json.dumps(params))) ) + def set_quantum_seed(seed: Optional[int]) -> None: """ Sets the seed for the random number generator used for quantum measurements. @@ -262,6 +269,7 @@ def set_quantum_seed(seed: Optional[int]) -> None: """ get_interpreter().set_quantum_seed(seed) + def set_classical_seed(seed: Optional[int]) -> None: """ Sets the seed for the random number generator used for standard @@ -273,6 +281,7 @@ def set_classical_seed(seed: Optional[int]) -> None: """ get_interpreter().set_classical_seed(seed) + class StateDump: """ A state dump returned from the Q# interpreter. @@ -309,6 +318,7 @@ def __str__(self) -> str: def _repr_html_(self) -> str: return self.__data._repr_html_() + def dump_machine() -> StateDump: """ Returns the sparse state vector of the simulator as a StateDump object. diff --git a/pip/qsharp/estimator/__init__.py b/pip/qsharp/estimator/__init__.py index 58dfeeea62..ef870f3dad 100644 --- a/pip/qsharp/estimator/__init__.py +++ b/pip/qsharp/estimator/__init__.py @@ -1,7 +1,22 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -from ._estimator import EstimatorError, LogicalCounts, EstimatorResult, QubitParams, QECScheme, MeasurementErrorRate, EstimatorQubitParams, EstimatorQecScheme, ProtocolSpecificDistillationUnitSpecification, DistillationUnitSpecification, ErrorBudgetPartition, EstimatorConstraints, EstimatorInputParamsItem, EstimatorParams +from ._estimator import ( + EstimatorError, + LogicalCounts, + EstimatorResult, + QubitParams, + QECScheme, + MeasurementErrorRate, + EstimatorQubitParams, + EstimatorQecScheme, + ProtocolSpecificDistillationUnitSpecification, + DistillationUnitSpecification, + ErrorBudgetPartition, + EstimatorConstraints, + EstimatorInputParamsItem, + EstimatorParams, +) __all__ = [ "EstimatorError", diff --git a/pip/qsharp/estimator/_estimator.py b/pip/qsharp/estimator/_estimator.py index c6c31d81a7..2047e51d2f 100644 --- a/pip/qsharp/estimator/_estimator.py +++ b/pip/qsharp/estimator/_estimator.py @@ -17,6 +17,7 @@ except ImportError: has_markdown = False + class EstimatorError(BaseException): """ An error returned from the resource estimation. @@ -29,6 +30,7 @@ def __init__(self, code: str, message: str): def __str__(self): return self.message + @dataclass class AutoValidatingParams: """ @@ -151,12 +153,12 @@ def check_instruction_set(name, value): one_qubit_gate_time: Optional[str] = validating_field(check_time) two_qubit_gate_time: Optional[str] = validating_field(check_time) t_gate_time: Optional[str] = validating_field(check_time) - one_qubit_measurement_error_rate: Union[ - None, float, MeasurementErrorRate - ] = validating_field(_check_error_rate_or_process_and_readout) - two_qubit_joint_measurement_error_rate: Union[ - None, float, MeasurementErrorRate - ] = validating_field(_check_error_rate_or_process_and_readout) + one_qubit_measurement_error_rate: Union[None, float, MeasurementErrorRate] = ( + validating_field(_check_error_rate_or_process_and_readout) + ) + two_qubit_joint_measurement_error_rate: Union[None, float, MeasurementErrorRate] = ( + validating_field(_check_error_rate_or_process_and_readout) + ) one_qubit_gate_error_rate: Optional[float] = validating_field(_check_error_rate) two_qubit_gate_error_rate: Optional[float] = validating_field(_check_error_rate) t_gate_error_rate: Optional[float] = validating_field(_check_error_rate) @@ -206,16 +208,16 @@ def as_dict(self, validate=True) -> Dict[str, Any]: qubit_params = super().as_dict(validate) if len(qubit_params) != 0: if isinstance(self.one_qubit_measurement_error_rate, MeasurementErrorRate): - qubit_params[ - "oneQubitMeasurementErrorRate" - ] = self.one_qubit_measurement_error_rate.as_dict(validate) + qubit_params["oneQubitMeasurementErrorRate"] = ( + self.one_qubit_measurement_error_rate.as_dict(validate) + ) if isinstance( self.two_qubit_joint_measurement_error_rate, MeasurementErrorRate ): - qubit_params[ - "twoQubitJointMeasurementErrorRate" - ] = self.two_qubit_joint_measurement_error_rate.as_dict(validate) + qubit_params["twoQubitJointMeasurementErrorRate"] = ( + self.two_qubit_joint_measurement_error_rate.as_dict(validate) + ) return qubit_params @@ -323,18 +325,18 @@ def as_dict(self, validate=True) -> Dict[str, Any]: self.physical_qubit_specification.as_dict(validate) ) if len(physical_qubit_specification_dict) != 0: - specification_dict[ - "physicalQubitSpecification" - ] = physical_qubit_specification_dict + specification_dict["physicalQubitSpecification"] = ( + physical_qubit_specification_dict + ) if self.logical_qubit_specification is not None: logical_qubit_specification_dict = ( self.logical_qubit_specification.as_dict(validate) ) if len(logical_qubit_specification_dict) != 0: - specification_dict[ - "logicalQubitSpecification" - ] = logical_qubit_specification_dict + specification_dict["logicalQubitSpecification"] = ( + logical_qubit_specification_dict + ) if self.logical_qubit_specification_first_round_override is not None: logical_qubit_specification_first_round_override_dict = ( @@ -549,7 +551,7 @@ def __init__(self, data: Union[Dict, List]): if isinstance(data, list) and len(data) == 1: data = data[0] if not EstimatorResult._is_succeeded(data): - raise EstimatorError(data['code'] ,data['message']) + raise EstimatorError(data["code"], data["message"]) if isinstance(data, dict): self._data = data @@ -561,7 +563,7 @@ def __init__(self, data: Union[Dict, List]): self.summary = HTMLWrapper(self._item_result_summary_table()) self.diagram = EstimatorResultDiagram(self.data().copy()) else: - self._error = EstimatorError(data['code'] ,data['message']) + self._error = EstimatorError(data["code"], data["message"]) elif isinstance(data, list): super().__init__( diff --git a/pip/qsharp/utils/_utils.py b/pip/qsharp/utils/_utils.py index fcdc2dcc87..6268c1801a 100644 --- a/pip/qsharp/utils/_utils.py +++ b/pip/qsharp/utils/_utils.py @@ -40,5 +40,10 @@ def dump_operation(operation: str, num_qubits: int) -> List[List[complex]]: if entry is None: matrix[i] += [complex(0, 0)] else: - matrix[i] += [complex(round(factor * entry.real, ndigits), round(factor * entry.imag, ndigits))] + matrix[i] += [ + complex( + round(factor * entry.real, ndigits), + round(factor * entry.imag, ndigits), + ) + ] return matrix diff --git a/pip/tests/test_interpreter.py b/pip/tests/test_interpreter.py index b5962d11a7..7d2cb2665f 100644 --- a/pip/tests/test_interpreter.py +++ b/pip/tests/test_interpreter.py @@ -49,22 +49,32 @@ def callback(output): ) assert called + def test_quantum_seed() -> None: e = Interpreter(TargetProfile.Unrestricted) e.set_quantum_seed(42) - value1 = e.interpret("{ use qs = Qubit[16]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }") + value1 = e.interpret( + "{ use qs = Qubit[16]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }" + ) e = Interpreter(TargetProfile.Unrestricted) e.set_quantum_seed(42) - value2 = e.interpret("{ use qs = Qubit[16]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }") + value2 = e.interpret( + "{ use qs = Qubit[16]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }" + ) assert value1 == value2 + def test_classical_seed() -> None: e = Interpreter(TargetProfile.Unrestricted) e.set_classical_seed(42) - value1 = e.interpret("{ mutable res = []; for _ in 0..15{ set res += [Microsoft.Quantum.Random.DrawRandomInt(0, 100)]; }; res }") + value1 = e.interpret( + "{ mutable res = []; for _ in 0..15{ set res += [Microsoft.Quantum.Random.DrawRandomInt(0, 100)]; }; res }" + ) e = Interpreter(TargetProfile.Unrestricted) e.set_classical_seed(42) - value2 = e.interpret("{ mutable res = []; for _ in 0..15{ set res += [Microsoft.Quantum.Random.DrawRandomInt(0, 100)]; }; res }") + value2 = e.interpret( + "{ mutable res = []; for _ in 0..15{ set res += [Microsoft.Quantum.Random.DrawRandomInt(0, 100)]; }; res }" + ) assert value1 == value2 diff --git a/pip/tests/test_qsharp.py b/pip/tests/test_qsharp.py index a7c3c65c92..4cd2316d84 100644 --- a/pip/tests/test_qsharp.py +++ b/pip/tests/test_qsharp.py @@ -33,30 +33,43 @@ def test_stdout_multiple_lines() -> None: assert f.getvalue() == "STATE:\n|0⟩: 1.0000+0.0000𝑖\nHello!\n" + def test_quantum_seed() -> None: qsharp.init(target_profile=qsharp.TargetProfile.Unrestricted) qsharp.set_quantum_seed(42) - value1 = qsharp.eval("{ use qs = Qubit[32]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }") + value1 = qsharp.eval( + "{ use qs = Qubit[32]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }" + ) qsharp.init(target_profile=qsharp.TargetProfile.Unrestricted) qsharp.set_quantum_seed(42) - value2 = qsharp.eval("{ use qs = Qubit[32]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }") + value2 = qsharp.eval( + "{ use qs = Qubit[32]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }" + ) assert value1 == value2 qsharp.set_quantum_seed(None) - value3 = qsharp.eval("{ use qs = Qubit[32]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }") + value3 = qsharp.eval( + "{ use qs = Qubit[32]; for q in qs { H(q); }; Microsoft.Quantum.Measurement.MResetEachZ(qs) }" + ) assert value1 != value3 def test_classical_seed() -> None: qsharp.init(target_profile=qsharp.TargetProfile.Unrestricted) qsharp.set_classical_seed(42) - value1 = qsharp.eval("{ mutable res = []; for _ in 0..15{ set res += [(Microsoft.Quantum.Random.DrawRandomInt(0, 100), Microsoft.Quantum.Random.DrawRandomDouble(0.0, 1.0))]; }; res }") + value1 = qsharp.eval( + "{ mutable res = []; for _ in 0..15{ set res += [(Microsoft.Quantum.Random.DrawRandomInt(0, 100), Microsoft.Quantum.Random.DrawRandomDouble(0.0, 1.0))]; }; res }" + ) qsharp.init(target_profile=qsharp.TargetProfile.Unrestricted) qsharp.set_classical_seed(42) - value2 = qsharp.eval("{ mutable res = []; for _ in 0..15{ set res += [(Microsoft.Quantum.Random.DrawRandomInt(0, 100), Microsoft.Quantum.Random.DrawRandomDouble(0.0, 1.0))]; }; res }") + value2 = qsharp.eval( + "{ mutable res = []; for _ in 0..15{ set res += [(Microsoft.Quantum.Random.DrawRandomInt(0, 100), Microsoft.Quantum.Random.DrawRandomDouble(0.0, 1.0))]; }; res }" + ) assert value1 == value2 qsharp.init(target_profile=qsharp.TargetProfile.Unrestricted) qsharp.set_classical_seed(None) - value3 = qsharp.eval("{ mutable res = []; for _ in 0..15{ set res += [(Microsoft.Quantum.Random.DrawRandomInt(0, 100), Microsoft.Quantum.Random.DrawRandomDouble(0.0, 1.0))]; }; res }") + value3 = qsharp.eval( + "{ mutable res = []; for _ in 0..15{ set res += [(Microsoft.Quantum.Random.DrawRandomInt(0, 100), Microsoft.Quantum.Random.DrawRandomDouble(0.0, 1.0))]; }; res }" + ) assert value1 != value3 @@ -86,34 +99,119 @@ def test_dump_machine() -> None: for idx in state_dump: assert idx in state_dump + def test_dump_operation() -> None: qsharp.init(target_profile=qsharp.TargetProfile.Unrestricted) res = qsharp.utils.dump_operation("qs => ()", 1) - assert res == [[complex(1.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(1.0, 0.0)]] + assert res == [ + [complex(1.0, 0.0), complex(0.0, 0.0)], + [complex(0.0, 0.0), complex(1.0, 0.0)], + ] res = qsharp.utils.dump_operation("qs => H(qs[0])", 1) - assert res == [[complex(0.707107, 0.0), complex(0.707107, 0.0)], - [complex(0.707107, 0.0), complex(-0.707107, 0.0)]] + assert res == [ + [complex(0.707107, 0.0), complex(0.707107, 0.0)], + [complex(0.707107, 0.0), complex(-0.707107, 0.0)], + ] res = qsharp.utils.dump_operation("qs => CNOT(qs[0], qs[1])", 2) - assert res == [[complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0)]] + assert res == [ + [complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], + [complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], + [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0)], + [complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0)], + ] res = qsharp.utils.dump_operation("qs => CCNOT(qs[0], qs[1], qs[2])", 3) - assert res == [[complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0)]] - qsharp.eval("operation ApplySWAP(qs : Qubit[]) : Unit is Ctl + Adj { SWAP(qs[0], qs[1]); }") + assert res == [ + [ + complex(1.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + ], + [ + complex(0.0, 0.0), + complex(1.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + ], + [ + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(1.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + ], + [ + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(1.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + ], + [ + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(1.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + ], + [ + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(1.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + ], + [ + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(1.0, 0.0), + ], + [ + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(0.0, 0.0), + complex(1.0, 0.0), + complex(0.0, 0.0), + ], + ] + qsharp.eval( + "operation ApplySWAP(qs : Qubit[]) : Unit is Ctl + Adj { SWAP(qs[0], qs[1]); }" + ) res = qsharp.utils.dump_operation("ApplySWAP", 2) - assert res == [[complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], - [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0)]] + assert res == [ + [complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], + [complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0)], + [complex(0.0, 0.0), complex(1.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0)], + [complex(0.0, 0.0), complex(0.0, 0.0), complex(0.0, 0.0), complex(1.0, 0.0)], + ] res = qsharp.utils.dump_operation("qs => ()", 8) for i in range(8): for j in range(8): diff --git a/samples/estimation/df-chemistry/chemistry.py b/samples/estimation/df-chemistry/chemistry.py index a11c41fe4d..a1a063676c 100644 --- a/samples/estimation/df-chemistry/chemistry.py +++ b/samples/estimation/df-chemistry/chemistry.py @@ -23,13 +23,13 @@ def from_file(self, file_url): it to get the input data for double-factorized chemistry algorithm.""" url = urlparse(file_url) - file_name = '' - if url.scheme in ['http', 'https']: + file_name = "" + if url.scheme in ["http", "https"]: # Download the file - file_name = url.path.rsplit('/', 1)[-1] - print(f'Downloading {file_url} to {file_name}...') + file_name = url.path.rsplit("/", 1)[-1] + print(f"Downloading {file_url} to {file_name}...") urlretrieve(file_url, file_name) - elif url.scheme in ['', 'file']: + elif url.scheme in ["", "file"]: # Use the whole URL as the file path file_name = file_url else: @@ -50,20 +50,20 @@ def from_file(self, file_url): if parse_header: # File header might not have key-value pairs in separate lines, # so accumulate the whole header first and then parse. - header += line + ' ' + header += line + " " if line.endswith("&END"): # Strip "&FCI" and "&END" rest_header = header[4:-4] # Find each key-value pair based on the "=" sign - while (ind := rest_header.find('=')) > -1: + while (ind := rest_header.find("=")) > -1: key = rest_header[0:ind].strip() - rest_header = rest_header[ind + 1:] + rest_header = rest_header[ind + 1 :] # Figure out which part of the rest is value: until # either whitespace or (if key is not ORBSYM) comma - ind = rest_header.find(',' if key != "ORBSYM" else ' ') + ind = rest_header.find("," if key != "ORBSYM" else " ") value = rest_header[0:ind].strip() - rest_header = rest_header[ind + 1:] + rest_header = rest_header[ind + 1 :] if key == "ORBSYM": value = value[:-1] header_values[key] = value @@ -71,7 +71,7 @@ def from_file(self, file_url): else: tokens = line.split() coefficient = float(tokens[0]) - indices = [int(str) - 1 for str in tokens[1:] if str != '0'] + indices = [int(str) - 1 for str in tokens[1:] if str != "0"] if len(indices) == 2: indices.sort() @@ -96,7 +96,7 @@ def from_file(self, file_url): symmetries.sort() self.two_body_terms.append((symmetries[0], coefficient)) - self.num_orbitals = int(header_values['NORB']) + self.num_orbitals = int(header_values["NORB"]) return self @@ -129,19 +129,23 @@ def process_fcidump(self, structure: FCIDumpFileContent, error: float): # In this step we compute R (`rank`) as in Eq. (7), h_{ij} # (`one_electron_vector`), and L_{ij}^{(r)} (`two_electron_vectors`). - (rank, eigenvalue_signs, one_electron_vector, two_electron_vectors) = \ + (rank, eigenvalue_signs, one_electron_vector, two_electron_vectors) = ( self.perform_svd(structure) + ) # This computes Eq. (15). It stores L^{-1}_{ij} into # `one_body_eigenvectors`, which is repurposed from # `one_electron_vector` and uses the same indices. It also returns the # Schatten norm, which is the first summand in Eq. (16). - (one_body_eigenvalues, one_body_eigenvectors, one_body_norm, norm2) = \ - self.process_one_body_term(structure.num_orbitals, - rank, - one_electron_vector, - eigenvalue_signs, - two_electron_vectors) + (one_body_eigenvalues, one_body_eigenvectors, one_body_norm, norm2) = ( + self.process_one_body_term( + structure.num_orbitals, + rank, + one_electron_vector, + eigenvalue_signs, + two_electron_vectors, + ) + ) assert len(one_body_eigenvectors) == structure.num_orbitals**2 @@ -149,23 +153,22 @@ def process_fcidump(self, structure: FCIDumpFileContent, error: float): # \lambda_m^{(r)} and eigenvectors $\vec R_{m,i}^{(r)}$. It does not # normalize the vectors, but returns the one- and two-norms of the # eigenvalues. - two_body_result = self.process_two_body_terms(structure.num_orbitals, - rank, - two_electron_vectors) + two_body_result = self.process_two_body_terms( + structure.num_orbitals, rank, two_electron_vectors + ) # Discard terms that will make the description exceed the error budget self.truncate_terms(structure.num_orbitals, error, two_body_result) # This computes the second summand in Eq. (16). two_body_norm = 0.0 - two_body_norm += sum(0.25 * norm * norm - for norm in two_body_result.one_norms) + two_body_norm += sum(0.25 * norm * norm for norm in two_body_result.one_norms) # Reshape the one body eigenvectors so that they are represented # row-wise in a 2D array - one_body_eigenvectors = np.reshape(one_body_eigenvectors, - (structure.num_orbitals, - structure.num_orbitals)) + one_body_eigenvectors = np.reshape( + one_body_eigenvectors, (structure.num_orbitals, structure.num_orbitals) + ) for i in range(len(two_body_result.eigenvectors)): cols = structure.num_orbitals @@ -175,7 +178,8 @@ def process_fcidump(self, structure: FCIDumpFileContent, error: float): # Reshape the two body eigenvectors so that they represented # row-wise in a 2D array two_body_result.eigenvectors[i] = np.reshape( - two_body_result.eigenvectors[i], (rows, cols)) + two_body_result.eigenvectors[i], (rows, cols) + ) self.num_orbitals = structure.num_orbitals # Use the post-truncation rank @@ -190,10 +194,11 @@ def process_fcidump(self, structure: FCIDumpFileContent, error: float): return self def combined_index(i: int, j: int) -> int: - return int(max(i, j)*(max(i, j) + 1) / 2 + min(i, j)) + return int(max(i, j) * (max(i, j) + 1) / 2 + min(i, j)) - def vectors_to_sym_mat(vector: npt.NDArray[float], dimension: int) -> \ - npt.NDArray[npt.NDArray[float]]: + def vectors_to_sym_mat( + vector: npt.NDArray[float], dimension: int + ) -> npt.NDArray[npt.NDArray[float]]: matrix = np.zeros((dimension, dimension), dtype=float) # Create lower triangular matrix @@ -208,12 +213,12 @@ def vectors_to_sym_mat(vector: npt.NDArray[float], dimension: int) -> \ return matrix @classmethod - def populate_two_body_terms(self, - two_body_terms: list[tuple[list[int], float]])\ - -> list[tuple[list[int], float]]: + def populate_two_body_terms( + self, two_body_terms: list[tuple[list[int], float]] + ) -> list[tuple[list[int], float]]: complete_two_body_terms = [] - for ([i, j, k, l], val) in two_body_terms: + for [i, j, k, l], val in two_body_terms: ii = self.combined_index(i, l) jj = self.combined_index(j, k) @@ -224,15 +229,14 @@ def populate_two_body_terms(self, return complete_two_body_terms @classmethod - def eigen_svd(self, - orbitals: int, - two_body_terms: list[tuple[list[int], float]]) \ - -> (int, list[int], list[float]): + def eigen_svd( + self, orbitals: int, two_body_terms: list[tuple[list[int], float]] + ) -> (int, list[int], list[float]): # combined = nC2 for n = orbitals combined = int(orbitals * (orbitals + 1) / 2) coeff_matrix = np.zeros((combined, combined), dtype=float) - for ([i, j], v) in two_body_terms: + for [i, j], v in two_body_terms: coeff_matrix[int(i)][int(j)] = v # Compute the eigen decomposition of the symmetric coefficient matrix @@ -256,30 +260,33 @@ def eigen_svd(self, return (cols, evals_signs.tolist(), scaled_evecs_1D) @classmethod - def perform_svd(self, structure: FCIDumpFileContent) \ - -> (int, npt.NDArray[int], npt.NDArray[float], npt.NDArray[float]): + def perform_svd( + self, structure: FCIDumpFileContent + ) -> (int, npt.NDArray[int], npt.NDArray[float], npt.NDArray[float]): - full_two_body_terms = \ - self.populate_two_body_terms(structure.two_body_terms) + full_two_body_terms = self.populate_two_body_terms(structure.two_body_terms) # Compute the eigen decomposition of two-electron terms - (rank, eigenvalue_signs, two_electron_vectors) = \ - self.eigen_svd(structure.num_orbitals, full_two_body_terms) - - length = self.combined_index(structure.num_orbitals - 1, - structure.num_orbitals - 1) + 1 + (rank, eigenvalue_signs, two_electron_vectors) = self.eigen_svd( + structure.num_orbitals, full_two_body_terms + ) + + length = ( + self.combined_index(structure.num_orbitals - 1, structure.num_orbitals - 1) + + 1 + ) one_electron_vector = np.zeros((length), dtype=float) # Collect one-electron terms into a single 2D array - for ([i, j], v) in structure.one_body_terms: + for [i, j], v in structure.one_body_terms: one_electron_vector[self.combined_index(i, j)] = v - return (rank, eigenvalue_signs, - one_electron_vector, two_electron_vectors) + return (rank, eigenvalue_signs, one_electron_vector, two_electron_vectors) @classmethod - def eigen_decomp(self, dimension: int, vector: npt.NDArray[float]) \ - -> (npt.NDArray[float], npt.NDArray[float], float, float): + def eigen_decomp( + self, dimension: int, vector: npt.NDArray[float] + ) -> (npt.NDArray[float], npt.NDArray[float], float, float): matrix = np.zeros((dimension, dimension), dtype=float) # Create lower triangular matrix @@ -291,18 +298,19 @@ def eigen_decomp(self, dimension: int, vector: npt.NDArray[float]) \ norm2 = np.linalg.norm(evals) (rows, cols) = np.shape(evecs) - evecs_1D = np.reshape(np.transpose(evecs), cols*rows) + evecs_1D = np.reshape(np.transpose(evecs), cols * rows) return (evals, evecs_1D, norm1, norm2) @classmethod - def process_one_body_term(self, - orbitals: int, - rank: int, - one_electron_vector: npt.NDArray[float], - eigenvalue_signs: npt.NDArray[bool], - two_electron_vectors: npt.NDArray[float]) \ - -> (list[float], npt.NDArray[float], float, float): + def process_one_body_term( + self, + orbitals: int, + rank: int, + one_electron_vector: npt.NDArray[float], + eigenvalue_signs: npt.NDArray[bool], + two_electron_vectors: npt.NDArray[float], + ) -> (list[float], npt.NDArray[float], float, float): # combined = nC2 for n = orbitals combined = int(orbitals * (orbitals + 1) / 2) @@ -311,8 +319,8 @@ def process_one_body_term(self, for l in range(rank): matrix = np.zeros((orbitals, orbitals), dtype=float) H_l = self.vectors_to_sym_mat( - two_electron_vectors[range(combined * l, combined * (l + 1))], - orbitals) + two_electron_vectors[range(combined * l, combined * (l + 1))], orbitals + ) H_issj = eigenvalue_signs[l] * np.matmul(H_l, H_l) @@ -325,18 +333,16 @@ def process_one_body_term(self, one_electron_vector += vector - (one_body_eigenvalues, one_body_eigenvectors, one_body_norm, norm2) = \ + (one_body_eigenvalues, one_body_eigenvectors, one_body_norm, norm2) = ( self.eigen_decomp(orbitals, one_electron_vector) + ) - return (one_body_eigenvalues, one_body_eigenvectors, - one_body_norm, norm2) + return (one_body_eigenvalues, one_body_eigenvectors, one_body_norm, norm2) @classmethod - def process_two_body_terms(self, - orbitals: int, - rank: int, - two_electron_vectors: npt.NDArray[float]) \ - -> TwoBodyResult: + def process_two_body_terms( + self, orbitals: int, rank: int, two_electron_vectors: npt.NDArray[float] + ) -> TwoBodyResult: two_body_eigenvectors = [] two_body_eigenvalues = [] @@ -345,7 +351,7 @@ def process_two_body_terms(self, combined = int(orbitals * (orbitals + 1) / 2) for i in range(rank): - matrix = two_electron_vectors[range(combined * i, combined * (i+1))] + matrix = two_electron_vectors[range(combined * i, combined * (i + 1))] (evals, evecs, norm1, norm2) = self.eigen_decomp(orbitals, matrix) two_body_eigenvalues.append(evals) @@ -353,20 +359,19 @@ def process_two_body_terms(self, one_norms.append(norm1) two_norms.append(norm2) - two_body_result = TwoBodyResult(two_body_eigenvalues, - two_body_eigenvectors, - one_norms, two_norms) + two_body_result = TwoBodyResult( + two_body_eigenvalues, two_body_eigenvectors, one_norms, two_norms + ) return two_body_result @classmethod - def truncate_terms(self, - orbitals: int, - error_eigenvalues: float, - two_body_result: TwoBodyResult): + def truncate_terms( + self, orbitals: int, error_eigenvalues: float, two_body_result: TwoBodyResult + ): values_with_error = [] - for (r, values) in enumerate(two_body_result.eigenvalues): - for (i, v) in enumerate(values): + for r, values in enumerate(two_body_result.eigenvalues): + for i, v in enumerate(values): error = abs(v) * two_body_result.two_norms[r] values_with_error.append((error, r, i)) @@ -377,7 +382,7 @@ def truncate_terms(self, # than the square of the input error total_error = 0 truncate = len(values_with_error) - for (i, (error, _, _)) in enumerate(values_with_error): + for i, (error, _, _) in enumerate(values_with_error): error_compare = error**2 if total_error + error_compare < error_eigenvalues**2: total_error += error_compare @@ -389,12 +394,12 @@ def truncate_terms(self, values_with_error = values_with_error[:truncate] indices_by_rank = [] - for (_, r, i) in values_with_error: + for _, r, i in values_with_error: while r >= len(indices_by_rank): indices_by_rank.append([]) indices_by_rank[r].append(i) - for (r, indices) in reversed(list(enumerate(indices_by_rank))): + for r, indices in reversed(list(enumerate(indices_by_rank))): if len(indices) == orbitals: # All indices are to be removed: fully remove the r^th entry # for the norms, eigenvalues and eigenvectors. @@ -405,39 +410,41 @@ def truncate_terms(self, else: indices.sort() # Remove only eigenvalues/vectors corresponding to `indices` - two_body_result.eigenvalues[r] = \ - np.delete(arr=two_body_result.eigenvalues[r], obj=indices) - arr = np.reshape(two_body_result.eigenvectors[r], - (orbitals, orbitals)) + two_body_result.eigenvalues[r] = np.delete( + arr=two_body_result.eigenvalues[r], obj=indices + ) + arr = np.reshape(two_body_result.eigenvectors[r], (orbitals, orbitals)) arr = np.delete(arr=arr, obj=indices, axis=0) (rows, cols) = np.shape(arr) - two_body_result.eigenvectors[r] = np.reshape(arr, rows*cols) + two_body_result.eigenvectors[r] = np.reshape(arr, rows * cols) # Check that same number of terms are removed for norms, # eigenvalues and eigenvectors - assert len(two_body_result.one_norms) == \ - len(two_body_result.two_norms) \ - and len(two_body_result.one_norms) == \ - len(two_body_result.eigenvalues) \ - and len(two_body_result.one_norms) == \ - len(two_body_result.eigenvectors) + assert ( + len(two_body_result.one_norms) == len(two_body_result.two_norms) + and len(two_body_result.one_norms) == len(two_body_result.eigenvalues) + and len(two_body_result.one_norms) == len(two_body_result.eigenvectors) + ) # Convert 2D array into string representation def ndarray2d_to_string(arr): str_arr = [] for elem in arr: - str_arr.append(np.array2string(elem, separator=',')) + str_arr.append(np.array2string(elem, separator=",")) return f"[{','.join(str_arr)}]" # The script takes one required positional argument, URI of the FCIDUMP file -parser = ArgumentParser(description='Double-factorized chemistry sample') +parser = ArgumentParser(description="Double-factorized chemistry sample") # Use n2-10e-8o as the default sample. # Pass a different filename to get estimates for different compounds -parser.add_argument('-f', '--fcidumpfile', - default='https://aka.ms/fcidump/n2-10e-8o', - help='Path to the FCIDUMP file describing the Hamiltonian') +parser.add_argument( + "-f", + "--fcidumpfile", + default="https://aka.ms/fcidump/n2-10e-8o", + help="Path to the FCIDUMP file describing the Hamiltonian", +) args = parser.parse_args() # ----- Read the FCIDUMP file and get resource estimates from Q# algorithm ----- @@ -448,16 +455,19 @@ def ndarray2d_to_string(arr): qsharp.init(project_root=".") # Construct the Q# operation call for which we need to perform resource estimate -str_one_body_eigenvalues = np.array2string(df.one_body_eigenvalues, - separator=',') +str_one_body_eigenvalues = np.array2string(df.one_body_eigenvalues, separator=",") str_one_body_eigenvectors = ndarray2d_to_string(df.one_body_eigenvectors) str_two_body_eigenvalues = ndarray2d_to_string(df.two_body_eigenvalues) -str_two_body_eigenvectors = "[" + \ - ','.join([ndarray2d_to_string(eigenvectors) - for eigenvectors in df.two_body_eigenvectors]) + "]" +str_two_body_eigenvectors = ( + "[" + + ",".join( + [ndarray2d_to_string(eigenvectors) for eigenvectors in df.two_body_eigenvectors] + ) + + "]" +) qsharp_string = ( "Microsoft.Quantum.Applications.Chemistry.DoubleFactorizedChemistry(" @@ -466,19 +476,26 @@ def ndarray2d_to_string(arr): f"{str_one_body_eigenvalues}, {str_one_body_eigenvectors}, " f"[1.0, size = {df.rank}], {str_two_body_eigenvalues}, " f"{str_two_body_eigenvectors})," - "Microsoft.Quantum.Applications.Chemistry.DoubleFactorizedChemistryParameters(0.001,))") + "Microsoft.Quantum.Applications.Chemistry.DoubleFactorizedChemistryParameters(0.001,))" +) # Get resource estimates -res = qsharp.estimate(qsharp_string, - params={"errorBudget": 0.01, - "qubitParams": {"name": "qubit_maj_ns_e6"}, - "qecScheme": {"name": "floquet_code"}}) +res = qsharp.estimate( + qsharp_string, + params={ + "errorBudget": 0.01, + "qubitParams": {"name": "qubit_maj_ns_e6"}, + "qecScheme": {"name": "floquet_code"}, + }, +) # Store estimates in json file -with open('resource_estimate.json', 'w') as f: +with open("resource_estimate.json", "w") as f: f.write(res.json) # Print high-level resource estimation results print(f"Algorithm runtime: {res['physicalCountsFormatted']['runtime']}") -print(f"Number of physical qubits required: {res['physicalCountsFormatted']['physicalQubits']}") +print( + f"Number of physical qubits required: {res['physicalCountsFormatted']['physicalQubits']}" +) print("For more detailed resource counts, see file resource_estimate.json")