From d015a7a7faf233baf5439b03fba8b6a0dfbfbc60 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sat, 23 Nov 2024 14:11:18 +0100 Subject: [PATCH 01/28] feat: adds print_instructions flag to qjit, and forwards to device constructor --- frontend/catalyst/device/qjit_device.py | 3 ++- frontend/catalyst/jit.py | 11 +++++++++-- frontend/catalyst/qfunc.py | 6 ++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/catalyst/device/qjit_device.py b/frontend/catalyst/device/qjit_device.py index 9543e742eb..3c6628349f 100644 --- a/frontend/catalyst/device/qjit_device.py +++ b/frontend/catalyst/device/qjit_device.py @@ -290,7 +290,7 @@ def extract_backend_info(device, capabilities: DeviceCapabilities) -> BackendInf return extract_backend_info(device, capabilities) @debug_logger_init - def __init__(self, original_device): + def __init__(self, original_device, print_instructions=False): self.original_device = original_device for key, value in original_device.__dict__.items(): @@ -307,6 +307,7 @@ def __init__(self, original_device): self.backend_name = backend.c_interface_name self.backend_lib = backend.lpath self.backend_kwargs = backend.kwargs + self.backend_kwargs["print_instructions"] = print_instructions # this includes 'print_instructions' as a keyword argument for the device constructor. self.capabilities = get_qjit_device_capabilities(original_device_capabilities) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 86722edd56..0b1db5b920 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -89,6 +89,7 @@ def qjit( seed=None, experimental_capture=False, circuit_transform_pipeline=None, + print_instructions=False ): # pylint: disable=too-many-arguments,unused-argument """A just-in-time decorator for PennyLane and JAX programs using Catalyst. @@ -155,6 +156,7 @@ def qjit( dictionaries of valid keyword arguments and values for the specific pass. The order of keys in this dictionary will determine the pass pipeline. If not specified, the default pass pipeline will be applied. + print_instructions (Optional[bool]): If set to True, instructions are printed when executing in the null device. Returns: QJIT object. @@ -432,11 +434,12 @@ def sum_abstracted(arr): """ kwargs = copy.copy(locals()) kwargs.pop("fn") + kwargs.pop("print_instructions") if fn is None: return functools.partial(qjit, **kwargs) - return QJIT(fn, CompileOptions(**kwargs)) + return QJIT(fn, CompileOptions(**kwargs), print_instructions) ## IMPL ## @@ -454,6 +457,7 @@ class QJIT(CatalystCallable): Args: fn (Callable): the quantum or classical function to compile compile_options (CompileOptions): compilation options to use + print_instructions (Optional[bool]): if set to True, instructions are printed when executing in the null device. :ivar original_function: This attribute stores `fn`, the quantum or classical function object to compile, as is, without any modifications @@ -464,7 +468,7 @@ class QJIT(CatalystCallable): """ @debug_logger_init - def __init__(self, fn, compile_options): + def __init__(self, fn, compile_options, print_instructions=False): functools.update_wrapper(self, fn) self.original_function = fn self.compile_options = compile_options @@ -512,6 +516,8 @@ def __init__(self, fn, compile_options): if self.user_sig is not None and not self.compile_options.static_argnums: self.aot_compile() + # flag for printing instructions in the null device + self.print_instructions = print_instructions super().__init__("user_function") @debug_logger @@ -677,6 +683,7 @@ def closure(qnode, *args, **kwargs): return QFunc.__call__( qnode, pass_pipeline=self.compile_options.circuit_transform_pipeline, + print_instructions=self.print_instructions, *args, **dict(params, **kwargs), ) diff --git a/frontend/catalyst/qfunc.py b/frontend/catalyst/qfunc.py index d0f41fe01b..1b4561c5da 100644 --- a/frontend/catalyst/qfunc.py +++ b/frontend/catalyst/qfunc.py @@ -121,8 +121,10 @@ def __call__(self, *args, **kwargs): if mcm_config.mcm_method == "one-shot": mcm_config.postselect_mode = mcm_config.postselect_mode or "hw-like" return Function(dynamic_one_shot(self, mcm_config=mcm_config))(*args, **kwargs) - - qjit_device = QJITDevice(self.device) + + # retrieve flag of whether or not to print instructions (in case we execute the pre-compiled program in the null device) + print_instructions = kwargs.pop("print_instructions", False) + qjit_device = QJITDevice(self.device, print_instructions) static_argnums = kwargs.pop("static_argnums", ()) out_tree_expected = kwargs.pop("_out_tree_expected", []) From f4170f2099b985d473ec0993ceb58230b15c071e Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sat, 23 Nov 2024 14:45:28 +0100 Subject: [PATCH 02/28] refact: change comment --- frontend/catalyst/device/qjit_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/catalyst/device/qjit_device.py b/frontend/catalyst/device/qjit_device.py index 3c6628349f..8b29eeebd3 100644 --- a/frontend/catalyst/device/qjit_device.py +++ b/frontend/catalyst/device/qjit_device.py @@ -307,7 +307,7 @@ def __init__(self, original_device, print_instructions=False): self.backend_name = backend.c_interface_name self.backend_lib = backend.lpath self.backend_kwargs = backend.kwargs - self.backend_kwargs["print_instructions"] = print_instructions # this includes 'print_instructions' as a keyword argument for the device constructor. + self.backend_kwargs["print_instructions"] = print_instructions # include 'print_instructions' as a keyword argument for the device constructor. self.capabilities = get_qjit_device_capabilities(original_device_capabilities) From 88ceb13924edaccdc1f4fe1039d8ced26931b271 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Thu, 28 Nov 2024 17:01:33 +0100 Subject: [PATCH 03/28] fix: initialize earlier print_instructions flag in QJIT class --- frontend/catalyst/jit.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 0b1db5b920..2e6d5c58d6 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -439,7 +439,7 @@ def sum_abstracted(arr): if fn is None: return functools.partial(qjit, **kwargs) - return QJIT(fn, CompileOptions(**kwargs), print_instructions) + return QJIT(fn, CompileOptions(**kwargs), print_instructions=print_instructions) ## IMPL ## @@ -469,6 +469,9 @@ class QJIT(CatalystCallable): @debug_logger_init def __init__(self, fn, compile_options, print_instructions=False): + # flag for printing instructions in the null device + self.print_instructions = print_instructions + functools.update_wrapper(self, fn) self.original_function = fn self.compile_options = compile_options @@ -516,8 +519,6 @@ def __init__(self, fn, compile_options, print_instructions=False): if self.user_sig is not None and not self.compile_options.static_argnums: self.aot_compile() - # flag for printing instructions in the null device - self.print_instructions = print_instructions super().__init__("user_function") @debug_logger From c63f33c201299ae3e8a61dfaf6765a80f837001e Mon Sep 17 00:00:00 2001 From: smml1996 Date: Thu, 28 Nov 2024 17:02:03 +0100 Subject: [PATCH 04/28] feat: prints instruction in null device --- .../null_qubit/InstructionStrBuilder.hpp | 316 ++++++++++++++++++ runtime/lib/backend/null_qubit/NullQubit.hpp | 119 +++++-- 2 files changed, 414 insertions(+), 21 deletions(-) create mode 100644 runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp new file mode 100644 index 0000000000..1200ea7e29 --- /dev/null +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -0,0 +1,316 @@ + +#pragma once + +#include +#include +#include +#include +#include + +#include "DataView.hpp" +#include "Types.h" + +namespace Catalyst::Runtime { + using namespace std; + + // string representation of observables + static const unordered_map obs_id_to_str = { + {ObsId::Identity, "Identity"}, + {ObsId::PauliX, "PauliX"}, + {ObsId::PauliY, "PauliY"}, + {ObsId::PauliZ, "PauliZ"}, + {ObsId::Hadamard, "Hadamard"}, + {ObsId::Hermitian, "Hermitian"}, + }; + + /** + * InstructionStrBuilder + * + * @brief This class is used by the null device (NullQubit.hpp) whenever the flag to print instructions (print_instructions) is set to true. It is in charge of building string repretations the operations invoked in the aformentioned device interface. + */ + + class InstructionStrBuilder { + private: + unordered_map obs_id_type_to_str{}; // whenever a new observable is created we store the corresponding string representation in this hashmap + + /** + * @brief Template function that returns the string representation of some object. + */ + template string element_to_str(const T &e) { + return to_string(e); + } + + /** + * @brief Template specialized to return the string representation of complex numbers. + */ + template string element_to_str(const complex &c) { + ostringstream oss; + oss << c.real(); + + if (c.imag() != 0) { + // to keep printing output as short as possible, we only print the imaginary part whenever is different from zero; + oss << " + " << c.imag()<<"i"; + } + return oss.str(); + } + + public: + InstructionStrBuilder() = default; + ~InstructionStrBuilder() = default; + + /** + * @brief This method is used to build a string representation of AllocateQubit(), AllocateQubits(), ReleaseQubit(), ReleaseAllQubits(), Measure() + */ + template string get_simple_op_str(const string &name, const T ¶m) { + ostringstream oss; + oss << name << "(" << param << ")"; + return oss.str(); + } + + /** + * @brief This method is used to build a string representation of Expval() and Var() + */ + string get_simple_op_str(const string &name, const ObsIdType &o) { + ostringstream oss; + oss << name << "(" << get_obs_str(o) << ")"; + return oss.str(); + } + + /** + * @brief This method is used to build a string representation of State(), Probs(), PartialProbs() + */ + template + string get_simple_op_str(const string &name, DataView &dataview) { + ostringstream oss; + oss << name << "(" << get_dataview_str(dataview) << ")"; + return oss.str(); + } + + /** + * @brief Takes as input a vector and returns its string representation. If with_brackets=True, the square brackets will be included to enclose the vector. E.g. + * - vector_to_string([0,1,2], false) returns "0, 1, 2" + * - vector_to_string([0,1,2], true) returns "[0, 1, 2]" + */ + template string vector_to_string(const vector& v, const bool &with_brackets = true) { + ostringstream oss; + if (with_brackets) oss << "["; + + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + oss << ", "; + } + oss << element_to_str(v[i]); + } + if (with_brackets) oss << "]"; + + return oss.str(); + } + + /** + * @brief This method is used to get the string representation of NamedOperation() + */ + string get_named_op_str(const std::string &name, const std::vector ¶ms, + const std::vector &wires, bool inverse = false, + const std::vector &controlled_wires = {}, + const std::vector &controlled_values = {}) { + ostringstream oss; + vector values_to_print; // Store the string representation of the parameters passed NamedOperation(). We only consider non-empty vectors to preserve printing-output simplicity. + + for (auto p : params) values_to_print.push_back(std::to_string(p)); + + if (wires.size() > 0){ + if (params.size()) { + // when an operation corresponds to a parametric gate, explicity specify the wires as a single vector. E.g Rx(3.14, wires=[0]) + values_to_print.push_back("wires=" + vector_to_string(wires, true)); + } else { + // Otherwise, we do not represent it as a vector but rather as a list separated with commas. E.g. CX(0,1) + values_to_print.push_back(vector_to_string(wires, false)); + } + } + + // if inverse is false, we will not print its value + if (inverse) values_to_print.push_back("inverse=True"); + + if (controlled_wires.size() > 0) values_to_print.push_back("control=" + vector_to_string(controlled_wires)); + + if (controlled_values.size() > 0) values_to_print.push_back("control_value=" + vector_to_string(controlled_values)); + + oss << name << "("; + for (auto i = 0; i < values_to_print.size(); i++) { + if (i > 0) { + oss << ", "; + } + oss << values_to_print[i]; + } + oss << ")"; + return oss.str(); + } + + /** + * @brief This method is used to get the string representation of MatrixOperation() + */ + string get_matrix_op_str(const std::vector> &matrix, + const std::vector &wires, bool inverse, + const std::vector &controlled_wires = {}, + const std::vector &controlled_values = {}, const string &name="MatrixOperation") { + ostringstream oss; + vector values_to_print; // Store the string representation of the parameters passed NamedOperation(). We only consider non-empty vectors to preserve printing-output simplicity. + + values_to_print.emplace_back(vector_to_string(matrix)); + + values_to_print.emplace_back("wires=" + vector_to_string(wires)); + + if (inverse) values_to_print.push_back("inverse=True"); + + if (controlled_wires.size() > 0) values_to_print.push_back("control=" + vector_to_string(controlled_wires)); + + if (controlled_values.size() > 0) values_to_print.push_back("control_value=" + vector_to_string(controlled_values)); + + oss << name << "("; + for (auto i = 0; i < values_to_print.size(); i++) { + if (i > 0) { + oss << ", "; + } + oss << values_to_print[i]; + } + + oss << ")"; + return oss.str(); + } + + /** + * @brief Every time Observable() is invoked in the null device interface, we invoke this function to create a new ObsIdType and its corresponding string representation. + */ + ObsIdType create_obs_str(ObsId obs_id, const std::vector> &matrix, + const std::vector &wires) { + ObsIdType new_id = obs_id_type_to_str.size(); + + if (obs_id == ObsId::Hermitian) { + obs_id_type_to_str.emplace(new_id, + get_matrix_op_str(matrix, wires, false, {}, {}, "Hermitian")); + } else { + auto it = obs_id_to_str.find(obs_id); + if (it != obs_id_to_str.end()) { + obs_id_type_to_str.emplace(new_id, get_named_op_str(it->second, {}, wires)); + } else { + RT_FAIL(("please check obs_id_to_str in file InstructionPrinter. Observation with ID" + to_string(obs_id) + "is not recognized.").c_str()); + } + } + + return new_id; + } + + /** + * @brief Every time TensorObservable() is invoked in the null device interface, we invoke this function to create a new ObsIdType and its corresponding string representation. + */ + ObsIdType create_tensor_obs_str(const std::vector &obs_keys) { + ostringstream oss; + for (auto i = 0 ; i < obs_keys.size(); i++) { + if (i > 0) { + oss << " ⊗ "; + } + oss << obs_id_type_to_str[obs_keys[i]]; + } + ObsIdType new_id = obs_id_type_to_str.size(); + obs_id_type_to_str[new_id] = oss.str(); + return new_id; + } + + /** + * @brief Every time HamiltonianObservable() is invoked in the null device interface, we invoke this function to create a new ObsIdType and its corresponding string representation. + */ + ObsIdType create_hamiltonian_obs_str(const std::vector &coeffs, const std::vector &obs_keys) { + RT_FAIL_IF(coeffs.size() != obs_keys.size(), "number of coefficients should match the number of observables"); + + ostringstream oss; + + for (auto i = 0; i < coeffs.size(); i++) { + if (i > 0) { + oss << " + "; + } + oss << coeffs[i] << "*" << obs_id_type_to_str[obs_keys[i]]; + } + + ObsIdType new_id = obs_id_type_to_str.size(); + obs_id_type_to_str[new_id] = oss.str(); + return new_id; + } + + /** + * @brief Getter function to retrieve the string representation of the observables we created. + */ + string get_obs_str(const ObsIdType &o) { + return obs_id_type_to_str.at(o); + } + + /** + * @brief Builds the string representation of DataView + */ + template + string get_dataview_str(DataView &dataview) { + ostringstream oss; + bool is_first = true; // boolean to help determine where to put commas + + oss << "["; + for (auto it = dataview.begin(); it != dataview.end(); it++) { + if (!is_first) { + oss << ", "; // if is not the first element then we add a comma to separate the elements + } + oss << element_to_str(*it); + is_first = false; + } + oss << "]"; + return oss.str(); + } + + /** + * @brief This method is used to get the string representation of Sample() and PartialSample() + */ + string get_samples_str(const string &name, DataView &samples, const size_t &shots, const vector &wires={}) { + ostringstream oss; + bool is_first = true; + oss << name << "(" << get_dataview_str(samples); + + if (wires.size() > 0) { + oss << ", wires=" << vector_to_string(wires); + } + + oss << ", shots=" << shots << ")"; + + return oss.str(); + } + + /** + * @brief This method is used to get the string representation of Counts() and PartialCounts(). + */ + string get_counts_str(const string &name, DataView &vals, DataView &counts, const size_t &shots, const vector &wires={}) { + RT_FAIL_IF(vals.size() != counts.size(), "number of eigenvalues does not matches counts"); + + ostringstream oss; + bool is_first = true; + oss << name << "("; + oss << "["; + auto it1 = vals.begin(); + auto it2 = counts.begin(); + for ( ; it1 != vals.end(); it1++, it2++) { + if (!is_first) { + oss << ", "; + } + // here we build a substring that represents a pair (eigenval, count) + oss << "(" << element_to_str(*it1) << ", " << element_to_str(*it2) << ")"; + is_first = false; + } + oss << "]"; // + + if (wires.size() > 0) { + oss << ", wires=" << vector_to_string(wires); + } + + oss << ", shots=" << shots; + + oss << ")"; + + return oss.str(); + } + }; +} diff --git a/runtime/lib/backend/null_qubit/NullQubit.hpp b/runtime/lib/backend/null_qubit/NullQubit.hpp index f5ea09c54d..4fbad68fe7 100644 --- a/runtime/lib/backend/null_qubit/NullQubit.hpp +++ b/runtime/lib/backend/null_qubit/NullQubit.hpp @@ -20,8 +20,10 @@ #include #include #include +#include #include "DataView.hpp" +#include "InstructionStrBuilder.hpp" #include "QuantumDevice.hpp" #include "QubitManager.hpp" #include "Types.h" @@ -40,7 +42,10 @@ namespace Catalyst::Runtime::Devices { * of the device; these are used to implement Quantum Instruction Set (QIS) instructions. */ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { - NullQubit(const std::string &kwargs = "{}") {} + NullQubit(const std::string &kwargs = "{}") { + std::unordered_map device_kwargs = Catalyst::Runtime::parse_kwargs(kwargs); + print_instructions = device_kwargs["print_instructions"] == "True" ? 1 : 0; + } ~NullQubit() = default; // LCOV_EXCL_LINE NullQubit &operator=(const NullQubit &) = delete; @@ -55,6 +60,10 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { */ auto AllocateQubit() -> QubitIdType { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("AllocateQubit", "") << std::endl; + } + num_qubits_++; // next_id return this->qubit_manager.Allocate(num_qubits_); } @@ -67,12 +76,20 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * @return `std::vector` */ auto AllocateQubits(size_t num_qubits) -> std::vector - { + { + bool prev_print_instructions = print_instructions; + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("AllocateQubits", num_qubits) << std::endl; + } + print_instructions = false; // set to false so we do not print instructions for each call to AllocateQubit() below. + if (!num_qubits) { return {}; } std::vector result(num_qubits); std::generate_n(result.begin(), num_qubits, [this]() { return AllocateQubit(); }); + + print_instructions = prev_print_instructions; // restore actual value of print_instructions return result; } @@ -81,6 +98,10 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { */ void ReleaseQubit(QubitIdType q) { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("ReleaseQubit", q) << std::endl; + } + if (!num_qubits_) { num_qubits_--; this->qubit_manager.Release(q); @@ -92,6 +113,10 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { */ void ReleaseAllQubits() { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("ReleaseAllQubits", "") << std::endl; + } + num_qubits_ = 0; this->qubit_manager.ReleaseAll(); } @@ -189,17 +214,23 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { const std::vector &controlled_wires = {}, const std::vector &controlled_values = {}) { + if (print_instructions) { + std::cout << instruction_str_builder.get_named_op_str(name, params, wires, inverse, controlled_wires, controlled_values) << std::endl; + } } /** * @brief Doesn't Apply a given matrix directly to the state vector of a device. * */ - void MatrixOperation(const std::vector> &, - const std::vector &, bool, + void MatrixOperation(const std::vector> &matrix, + const std::vector &wires, bool inverse, const std::vector &controlled_wires = {}, const std::vector &controlled_values = {}) { + if (print_instructions) { + std::cout << instruction_str_builder.get_matrix_op_str(matrix, wires, inverse, controlled_wires, controlled_values) << std::endl; + } } /** @@ -208,10 +239,10 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * * @return `ObsIdType` Index of the constructed observable */ - auto Observable(ObsId, const std::vector> &, - const std::vector &) -> ObsIdType + auto Observable(ObsId obs_id, const std::vector> &matrix, + const std::vector &wires) -> ObsIdType { - return 0.0; + return instruction_str_builder.create_obs_str(obs_id, matrix, wires); } /** @@ -219,17 +250,19 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * * @return `ObsIdType` Index of the constructed observable */ - auto TensorObservable(const std::vector &) -> ObsIdType { return 0.0; } + auto TensorObservable(const std::vector &obs_keys) -> ObsIdType { + return instruction_str_builder.create_tensor_obs_str(obs_keys); + } /** * @brief Doesn't Construct a Hamiltonian observable. * * @return `ObsIdType` Index of the constructed observable */ - auto HamiltonianObservable(const std::vector &, const std::vector &) + auto HamiltonianObservable(const std::vector &matrix, const std::vector &obs_keys) -> ObsIdType { - return 0.0; + return instruction_str_builder.create_hamiltonian_obs_str(matrix, obs_keys); } /** @@ -237,35 +270,61 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * * @return `double` The expected value */ - auto Expval(ObsIdType) -> double { return 0.0; } + auto Expval(ObsIdType o) -> double { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("Expval", o) << std::endl; + } + return 0.0; + } /** * @brief Doesn't Compute the variance of an observable. * * @return `double` The variance */ - auto Var(ObsIdType) -> double { return 0.0; } + auto Var(ObsIdType o) -> double { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("Var", o) << std::endl; + } + return 0.0; + } /** * @brief Doesn't Get the state-vector of a device. */ - void State(DataView, 1> &) {} + void State(DataView, 1> &state) { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("State", state) << std::endl; + } + } /** * @brief Doesn't Compute the probabilities of each computational basis state. */ - void Probs(DataView &) {} + void Probs(DataView &probs) { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("Probs", probs) << std::endl; + } + } /** * @brief Doesn't Compute the probabilities for a subset of the full system. */ - void PartialProbs(DataView &, const std::vector &) {} + void PartialProbs(DataView &probs, const std::vector &wires) { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("PartialProbs", probs) << std::endl; + } + } /** * @brief Doesn't Compute samples with the number of shots on the entire wires, * returing raw samples. */ - void Sample(DataView &, size_t) {} + void Sample(DataView &samples, size_t shots) { + if (print_instructions) { + std::cout << instruction_str_builder.get_samples_str("Sample", samples, shots) << std::endl; + } + } /** * @brief Doesn't Compute partial samples with the number of shots on `wires`, @@ -275,21 +334,33 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * shape `shots * numWires`. The built-in iterator in `DataView` * iterates over all elements of `samples` row-wise. */ - void PartialSample(DataView &, const std::vector &, size_t) {} + void PartialSample(DataView &samples, const std::vector &wires, size_t shots) { + if (print_instructions) { + std::cout << instruction_str_builder.get_samples_str("PartialSample", samples, shots, wires) << std::endl; + } + + } /** * @brief Doesn't Sample with the number of shots on the entire wires, returning the * number of counts for each sample. */ - void Counts(DataView &, DataView &, size_t) {} + void Counts(DataView &eigen_vals, DataView &counts, size_t shots) { + if (print_instructions) { + std::cout << instruction_str_builder.get_counts_str("Counts", eigen_vals, counts, shots) << std::endl; + } + } /** * @brief Doesn't Partial sample with the number of shots on `wires`, returning the * number of counts for each sample. */ - void PartialCounts(DataView &, DataView &, - const std::vector &, size_t) + void PartialCounts(DataView &eigen_vals, DataView &counts, + const std::vector &wires, size_t shots) { + if (print_instructions) { + std::cout << instruction_str_builder.get_counts_str("PartialCounts", eigen_vals, counts, shots, wires) << std::endl; + } } /** @@ -297,8 +368,12 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * * @return `Result` The measurement result */ - auto Measure(QubitIdType, std::optional) -> Result + auto Measure(QubitIdType q, std::optional postselect) -> Result { + if (print_instructions) { + std::cout << instruction_str_builder.get_simple_op_str("Measure", q) << std::endl; + } + bool *ret = (bool *)malloc(sizeof(bool)); *ret = true; return ret; @@ -318,7 +393,9 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { } private: + bool print_instructions; std::size_t num_qubits_{0}; Catalyst::Runtime::QubitManager qubit_manager{}; + Catalyst::Runtime::InstructionStrBuilder instruction_str_builder{}; }; } // namespace Catalyst::Runtime::Devices From f96644637c7368e0d670596b10efd3bcfde2a6b3 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Fri, 29 Nov 2024 09:11:49 +0100 Subject: [PATCH 05/28] refact: makes some funcs private --- .../null_qubit/InstructionStrBuilder.hpp | 80 +++++++++---------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 1200ea7e29..218a14ca46 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -54,6 +54,46 @@ namespace Catalyst::Runtime { return oss.str(); } + /** + * @brief Takes as input a vector and returns its string representation. If with_brackets=True, the square brackets will be included to enclose the vector. E.g. + * - vector_to_string([0,1,2], false) returns "0, 1, 2" + * - vector_to_string([0,1,2], true) returns "[0, 1, 2]" + */ + template string vector_to_string(const vector& v, const bool &with_brackets = true) { + ostringstream oss; + if (with_brackets) oss << "["; + + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + oss << ", "; + } + oss << element_to_str(v[i]); + } + if (with_brackets) oss << "]"; + + return oss.str(); + } + + /** + * @brief Builds the string representation of DataView + */ + template + string get_dataview_str(DataView &dataview) { + ostringstream oss; + bool is_first = true; // boolean to help determine where to put commas + + oss << "["; + for (auto it = dataview.begin(); it != dataview.end(); it++) { + if (!is_first) { + oss << ", "; // if is not the first element then we add a comma to separate the elements + } + oss << element_to_str(*it); + is_first = false; + } + oss << "]"; + return oss.str(); + } + public: InstructionStrBuilder() = default; ~InstructionStrBuilder() = default; @@ -86,26 +126,6 @@ namespace Catalyst::Runtime { return oss.str(); } - /** - * @brief Takes as input a vector and returns its string representation. If with_brackets=True, the square brackets will be included to enclose the vector. E.g. - * - vector_to_string([0,1,2], false) returns "0, 1, 2" - * - vector_to_string([0,1,2], true) returns "[0, 1, 2]" - */ - template string vector_to_string(const vector& v, const bool &with_brackets = true) { - ostringstream oss; - if (with_brackets) oss << "["; - - for (size_t i = 0; i < v.size(); i++) { - if (i > 0) { - oss << ", "; - } - oss << element_to_str(v[i]); - } - if (with_brackets) oss << "]"; - - return oss.str(); - } - /** * @brief This method is used to get the string representation of NamedOperation() */ @@ -243,26 +263,6 @@ namespace Catalyst::Runtime { return obs_id_type_to_str.at(o); } - /** - * @brief Builds the string representation of DataView - */ - template - string get_dataview_str(DataView &dataview) { - ostringstream oss; - bool is_first = true; // boolean to help determine where to put commas - - oss << "["; - for (auto it = dataview.begin(); it != dataview.end(); it++) { - if (!is_first) { - oss << ", "; // if is not the first element then we add a comma to separate the elements - } - oss << element_to_str(*it); - is_first = false; - } - oss << "]"; - return oss.str(); - } - /** * @brief This method is used to get the string representation of Sample() and PartialSample() */ From 27da58db8b4a788de4e8b7c016ccab967a3c583f Mon Sep 17 00:00:00 2001 From: smml1996 Date: Fri, 29 Nov 2024 12:18:53 +0100 Subject: [PATCH 06/28] fix: output hamiltonian does not has consecutive + - --- .../backend/null_qubit/InstructionStrBuilder.hpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 218a14ca46..26a68c5b08 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -93,7 +93,7 @@ namespace Catalyst::Runtime { oss << "]"; return oss.str(); } - + public: InstructionStrBuilder() = default; ~InstructionStrBuilder() = default; @@ -246,9 +246,16 @@ namespace Catalyst::Runtime { for (auto i = 0; i < coeffs.size(); i++) { if (i > 0) { - oss << " + "; + if (coeffs[i] > 0) { + oss << " + "; + }else if(coeffs[i] < 0) { + oss << " "; + } + } - oss << coeffs[i] << "*" << obs_id_type_to_str[obs_keys[i]]; + + if (coeffs[i] != 0) + oss << coeffs[i] << "*" << obs_id_type_to_str[obs_keys[i]]; } ObsIdType new_id = obs_id_type_to_str.size(); From 7add3c3ac4310882b170b2eda2a0b179c39bd9a3 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Fri, 29 Nov 2024 12:28:45 +0100 Subject: [PATCH 07/28] refact: make format --- frontend/catalyst/device/qjit_device.py | 4 +- frontend/catalyst/jit.py | 2 +- frontend/catalyst/qfunc.py | 2 +- .../null_qubit/InstructionStrBuilder.hpp | 580 ++++++++++-------- runtime/lib/backend/null_qubit/NullQubit.hpp | 86 ++- 5 files changed, 373 insertions(+), 301 deletions(-) diff --git a/frontend/catalyst/device/qjit_device.py b/frontend/catalyst/device/qjit_device.py index 8b29eeebd3..6487874f85 100644 --- a/frontend/catalyst/device/qjit_device.py +++ b/frontend/catalyst/device/qjit_device.py @@ -307,7 +307,9 @@ def __init__(self, original_device, print_instructions=False): self.backend_name = backend.c_interface_name self.backend_lib = backend.lpath self.backend_kwargs = backend.kwargs - self.backend_kwargs["print_instructions"] = print_instructions # include 'print_instructions' as a keyword argument for the device constructor. + self.backend_kwargs["print_instructions"] = ( + print_instructions # include 'print_instructions' as a keyword argument for the device constructor. + ) self.capabilities = get_qjit_device_capabilities(original_device_capabilities) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 2e6d5c58d6..63c7089e15 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -471,7 +471,7 @@ class QJIT(CatalystCallable): def __init__(self, fn, compile_options, print_instructions=False): # flag for printing instructions in the null device self.print_instructions = print_instructions - + functools.update_wrapper(self, fn) self.original_function = fn self.compile_options = compile_options diff --git a/frontend/catalyst/qfunc.py b/frontend/catalyst/qfunc.py index 1b4561c5da..60d90b2846 100644 --- a/frontend/catalyst/qfunc.py +++ b/frontend/catalyst/qfunc.py @@ -121,7 +121,7 @@ def __call__(self, *args, **kwargs): if mcm_config.mcm_method == "one-shot": mcm_config.postselect_mode = mcm_config.postselect_mode or "hw-like" return Function(dynamic_one_shot(self, mcm_config=mcm_config))(*args, **kwargs) - + # retrieve flag of whether or not to print instructions (in case we execute the pre-compiled program in the null device) print_instructions = kwargs.pop("print_instructions", False) qjit_device = QJITDevice(self.device, print_instructions) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 26a68c5b08..6fcdc2bd14 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -3,321 +3,365 @@ #include #include -#include #include #include +#include #include "DataView.hpp" #include "Types.h" namespace Catalyst::Runtime { - using namespace std; - - // string representation of observables - static const unordered_map obs_id_to_str = { - {ObsId::Identity, "Identity"}, - {ObsId::PauliX, "PauliX"}, - {ObsId::PauliY, "PauliY"}, - {ObsId::PauliZ, "PauliZ"}, - {ObsId::Hadamard, "Hadamard"}, - {ObsId::Hermitian, "Hermitian"}, - }; +using namespace std; + +// string representation of observables +static const unordered_map obs_id_to_str = { + {ObsId::Identity, "Identity"}, {ObsId::PauliX, "PauliX"}, {ObsId::PauliY, "PauliY"}, + {ObsId::PauliZ, "PauliZ"}, {ObsId::Hadamard, "Hadamard"}, {ObsId::Hermitian, "Hermitian"}, +}; + +/** + * InstructionStrBuilder + * + * @brief This class is used by the null device (NullQubit.hpp) whenever the flag to print + * instructions (print_instructions) is set to true. It is in charge of building string repretations + * the operations invoked in the aformentioned device interface. + */ + +class InstructionStrBuilder { + private: + unordered_map + obs_id_type_to_str{}; // whenever a new observable is created we store the corresponding + // string representation in this hashmap /** - * InstructionStrBuilder - * - * @brief This class is used by the null device (NullQubit.hpp) whenever the flag to print instructions (print_instructions) is set to true. It is in charge of building string repretations the operations invoked in the aformentioned device interface. + * @brief Template function that returns the string representation of some object. */ + template string element_to_str(const T &e) { return to_string(e); } - class InstructionStrBuilder { - private: - unordered_map obs_id_type_to_str{}; // whenever a new observable is created we store the corresponding string representation in this hashmap - - /** - * @brief Template function that returns the string representation of some object. - */ - template string element_to_str(const T &e) { - return to_string(e); - } - - /** - * @brief Template specialized to return the string representation of complex numbers. - */ - template string element_to_str(const complex &c) { - ostringstream oss; - oss << c.real(); - - if (c.imag() != 0) { - // to keep printing output as short as possible, we only print the imaginary part whenever is different from zero; - oss << " + " << c.imag()<<"i"; - } - return oss.str(); - } + /** + * @brief Template specialized to return the string representation of complex numbers. + */ + template string element_to_str(const complex &c) + { + ostringstream oss; + oss << c.real(); + + if (c.imag() != 0) { + // to keep printing output as short as possible, we only print the imaginary part + // whenever is different from zero; + oss << " + " << c.imag() << "i"; + } + return oss.str(); + } - /** - * @brief Takes as input a vector and returns its string representation. If with_brackets=True, the square brackets will be included to enclose the vector. E.g. - * - vector_to_string([0,1,2], false) returns "0, 1, 2" - * - vector_to_string([0,1,2], true) returns "[0, 1, 2]" - */ - template string vector_to_string(const vector& v, const bool &with_brackets = true) { - ostringstream oss; - if (with_brackets) oss << "["; - - for (size_t i = 0; i < v.size(); i++) { - if (i > 0) { - oss << ", "; - } - oss << element_to_str(v[i]); - } - if (with_brackets) oss << "]"; + /** + * @brief Takes as input a vector and returns its string representation. If with_brackets=True, + * the square brackets will be included to enclose the vector. E.g. + * - vector_to_string([0,1,2], false) returns "0, 1, 2" + * - vector_to_string([0,1,2], true) returns "[0, 1, 2]" + */ + template + string vector_to_string(const vector &v, const bool &with_brackets = true) + { + ostringstream oss; + if (with_brackets) + oss << "["; - return oss.str(); + for (size_t i = 0; i < v.size(); i++) { + if (i > 0) { + oss << ", "; } + oss << element_to_str(v[i]); + } + if (with_brackets) + oss << "]"; - /** - * @brief Builds the string representation of DataView - */ - template - string get_dataview_str(DataView &dataview) { - ostringstream oss; - bool is_first = true; // boolean to help determine where to put commas + return oss.str(); + } - oss << "["; - for (auto it = dataview.begin(); it != dataview.end(); it++) { - if (!is_first) { - oss << ", "; // if is not the first element then we add a comma to separate the elements - } - oss << element_to_str(*it); - is_first = false; + /** + * @brief Builds the string representation of DataView + */ + template string get_dataview_str(DataView &dataview) + { + ostringstream oss; + bool is_first = true; // boolean to help determine where to put commas + + oss << "["; + for (auto it = dataview.begin(); it != dataview.end(); it++) { + if (!is_first) { + oss << ", "; // if is not the first element then we add a comma to separate the + // elements } - oss << "]"; - return oss.str(); + oss << element_to_str(*it); + is_first = false; } + oss << "]"; + return oss.str(); + } - public: - InstructionStrBuilder() = default; - ~InstructionStrBuilder() = default; - - /** - * @brief This method is used to build a string representation of AllocateQubit(), AllocateQubits(), ReleaseQubit(), ReleaseAllQubits(), Measure() - */ - template string get_simple_op_str(const string &name, const T ¶m) { - ostringstream oss; - oss << name << "(" << param << ")"; - return oss.str(); - } + public: + InstructionStrBuilder() = default; + ~InstructionStrBuilder() = default; - /** - * @brief This method is used to build a string representation of Expval() and Var() - */ - string get_simple_op_str(const string &name, const ObsIdType &o) { - ostringstream oss; - oss << name << "(" << get_obs_str(o) << ")"; - return oss.str(); - } + /** + * @brief This method is used to build a string representation of AllocateQubit(), + * AllocateQubits(), ReleaseQubit(), ReleaseAllQubits(), Measure() + */ + template string get_simple_op_str(const string &name, const T ¶m) + { + ostringstream oss; + oss << name << "(" << param << ")"; + return oss.str(); + } - /** - * @brief This method is used to build a string representation of State(), Probs(), PartialProbs() - */ - template - string get_simple_op_str(const string &name, DataView &dataview) { - ostringstream oss; - oss << name << "(" << get_dataview_str(dataview) << ")"; - return oss.str(); - } + /** + * @brief This method is used to build a string representation of Expval() and Var() + */ + string get_simple_op_str(const string &name, const ObsIdType &o) + { + ostringstream oss; + oss << name << "(" << get_obs_str(o) << ")"; + return oss.str(); + } + + /** + * @brief This method is used to build a string representation of State(), Probs(), + * PartialProbs() + */ + template + string get_simple_op_str(const string &name, DataView &dataview) + { + ostringstream oss; + oss << name << "(" << get_dataview_str(dataview) << ")"; + return oss.str(); + } - /** - * @brief This method is used to get the string representation of NamedOperation() - */ - string get_named_op_str(const std::string &name, const std::vector ¶ms, + /** + * @brief This method is used to get the string representation of NamedOperation() + */ + string get_named_op_str(const std::string &name, const std::vector ¶ms, const std::vector &wires, bool inverse = false, const std::vector &controlled_wires = {}, - const std::vector &controlled_values = {}) { - ostringstream oss; - vector values_to_print; // Store the string representation of the parameters passed NamedOperation(). We only consider non-empty vectors to preserve printing-output simplicity. - - for (auto p : params) values_to_print.push_back(std::to_string(p)); - - if (wires.size() > 0){ - if (params.size()) { - // when an operation corresponds to a parametric gate, explicity specify the wires as a single vector. E.g Rx(3.14, wires=[0]) - values_to_print.push_back("wires=" + vector_to_string(wires, true)); - } else { - // Otherwise, we do not represent it as a vector but rather as a list separated with commas. E.g. CX(0,1) - values_to_print.push_back(vector_to_string(wires, false)); - } - } + const std::vector &controlled_values = {}) + { + ostringstream oss; + vector values_to_print; // Store the string representation of the parameters passed + // NamedOperation(). We only consider non-empty vectors to + // preserve printing-output simplicity. + + for (auto p : params) + values_to_print.push_back(std::to_string(p)); + + if (wires.size() > 0) { + if (params.size()) { + // when an operation corresponds to a parametric gate, explicity specify the wires + // as a single vector. E.g Rx(3.14, wires=[0]) + values_to_print.push_back("wires=" + vector_to_string(wires, true)); + } + else { + // Otherwise, we do not represent it as a vector but rather as a list separated with + // commas. E.g. CX(0,1) + values_to_print.push_back(vector_to_string(wires, false)); + } + } - // if inverse is false, we will not print its value - if (inverse) values_to_print.push_back("inverse=True"); + // if inverse is false, we will not print its value + if (inverse) + values_to_print.push_back("inverse=True"); - if (controlled_wires.size() > 0) values_to_print.push_back("control=" + vector_to_string(controlled_wires)); + if (controlled_wires.size() > 0) + values_to_print.push_back("control=" + vector_to_string(controlled_wires)); - if (controlled_values.size() > 0) values_to_print.push_back("control_value=" + vector_to_string(controlled_values)); + if (controlled_values.size() > 0) + values_to_print.push_back("control_value=" + vector_to_string(controlled_values)); - oss << name << "("; - for (auto i = 0; i < values_to_print.size(); i++) { - if (i > 0) { - oss << ", "; - } - oss << values_to_print[i]; - } - oss << ")"; - return oss.str(); + oss << name << "("; + for (auto i = 0; i < values_to_print.size(); i++) { + if (i > 0) { + oss << ", "; } + oss << values_to_print[i]; + } + oss << ")"; + return oss.str(); + } - /** - * @brief This method is used to get the string representation of MatrixOperation() - */ - string get_matrix_op_str(const std::vector> &matrix, - const std::vector &wires, bool inverse, - const std::vector &controlled_wires = {}, - const std::vector &controlled_values = {}, const string &name="MatrixOperation") { - ostringstream oss; - vector values_to_print; // Store the string representation of the parameters passed NamedOperation(). We only consider non-empty vectors to preserve printing-output simplicity. + /** + * @brief This method is used to get the string representation of MatrixOperation() + */ + string get_matrix_op_str(const std::vector> &matrix, + const std::vector &wires, bool inverse, + const std::vector &controlled_wires = {}, + const std::vector &controlled_values = {}, + const string &name = "MatrixOperation") + { + ostringstream oss; + vector values_to_print; // Store the string representation of the parameters passed + // NamedOperation(). We only consider non-empty vectors to + // preserve printing-output simplicity. + + values_to_print.emplace_back(vector_to_string(matrix)); + + values_to_print.emplace_back("wires=" + vector_to_string(wires)); + + if (inverse) + values_to_print.push_back("inverse=True"); + + if (controlled_wires.size() > 0) + values_to_print.push_back("control=" + vector_to_string(controlled_wires)); + + if (controlled_values.size() > 0) + values_to_print.push_back("control_value=" + vector_to_string(controlled_values)); + + oss << name << "("; + for (auto i = 0; i < values_to_print.size(); i++) { + if (i > 0) { + oss << ", "; + } + oss << values_to_print[i]; + } - values_to_print.emplace_back(vector_to_string(matrix)); + oss << ")"; + return oss.str(); + } - values_to_print.emplace_back("wires=" + vector_to_string(wires)); - - if (inverse) values_to_print.push_back("inverse=True"); + /** + * @brief Every time Observable() is invoked in the null device interface, we invoke this + * function to create a new ObsIdType and its corresponding string representation. + */ + ObsIdType create_obs_str(ObsId obs_id, const std::vector> &matrix, + const std::vector &wires) + { + ObsIdType new_id = obs_id_type_to_str.size(); + + if (obs_id == ObsId::Hermitian) { + obs_id_type_to_str.emplace( + new_id, get_matrix_op_str(matrix, wires, false, {}, {}, "Hermitian")); + } + else { + auto it = obs_id_to_str.find(obs_id); + if (it != obs_id_to_str.end()) { + obs_id_type_to_str.emplace(new_id, get_named_op_str(it->second, {}, wires)); + } + else { + RT_FAIL( + ("please check obs_id_to_str in file InstructionPrinter. Observation with ID" + + to_string(obs_id) + "is not recognized.") + .c_str()); + } + } - if (controlled_wires.size() > 0) values_to_print.push_back("control=" + vector_to_string(controlled_wires)); + return new_id; + } - if (controlled_values.size() > 0) values_to_print.push_back("control_value=" + vector_to_string(controlled_values)); + /** + * @brief Every time TensorObservable() is invoked in the null device interface, we invoke this + * function to create a new ObsIdType and its corresponding string representation. + */ + ObsIdType create_tensor_obs_str(const std::vector &obs_keys) + { + ostringstream oss; + for (auto i = 0; i < obs_keys.size(); i++) { + if (i > 0) { + oss << " ⊗ "; + } + oss << obs_id_type_to_str[obs_keys[i]]; + } + ObsIdType new_id = obs_id_type_to_str.size(); + obs_id_type_to_str[new_id] = oss.str(); + return new_id; + } - oss << name << "("; - for (auto i = 0; i < values_to_print.size(); i++) { - if (i > 0) { - oss << ", "; - } - oss << values_to_print[i]; + /** + * @brief Every time HamiltonianObservable() is invoked in the null device interface, we invoke + * this function to create a new ObsIdType and its corresponding string representation. + */ + ObsIdType create_hamiltonian_obs_str(const std::vector &coeffs, + const std::vector &obs_keys) + { + RT_FAIL_IF(coeffs.size() != obs_keys.size(), + "number of coefficients should match the number of observables"); + + ostringstream oss; + + for (auto i = 0; i < coeffs.size(); i++) { + if (i > 0) { + if (coeffs[i] > 0) { + oss << " + "; } - - oss << ")"; - return oss.str(); - } - - /** - * @brief Every time Observable() is invoked in the null device interface, we invoke this function to create a new ObsIdType and its corresponding string representation. - */ - ObsIdType create_obs_str(ObsId obs_id, const std::vector> &matrix, - const std::vector &wires) { - ObsIdType new_id = obs_id_type_to_str.size(); - - if (obs_id == ObsId::Hermitian) { - obs_id_type_to_str.emplace(new_id, - get_matrix_op_str(matrix, wires, false, {}, {}, "Hermitian")); - } else { - auto it = obs_id_to_str.find(obs_id); - if (it != obs_id_to_str.end()) { - obs_id_type_to_str.emplace(new_id, get_named_op_str(it->second, {}, wires)); - } else { - RT_FAIL(("please check obs_id_to_str in file InstructionPrinter. Observation with ID" + to_string(obs_id) + "is not recognized.").c_str()); - } + else if (coeffs[i] < 0) { + oss << " "; } - - return new_id; } - /** - * @brief Every time TensorObservable() is invoked in the null device interface, we invoke this function to create a new ObsIdType and its corresponding string representation. - */ - ObsIdType create_tensor_obs_str(const std::vector &obs_keys) { - ostringstream oss; - for (auto i = 0 ; i < obs_keys.size(); i++) { - if (i > 0) { - oss << " ⊗ "; - } - oss << obs_id_type_to_str[obs_keys[i]]; - } - ObsIdType new_id = obs_id_type_to_str.size(); - obs_id_type_to_str[new_id] = oss.str(); - return new_id; - } + if (coeffs[i] != 0) + oss << coeffs[i] << "*" << obs_id_type_to_str[obs_keys[i]]; + } - /** - * @brief Every time HamiltonianObservable() is invoked in the null device interface, we invoke this function to create a new ObsIdType and its corresponding string representation. - */ - ObsIdType create_hamiltonian_obs_str(const std::vector &coeffs, const std::vector &obs_keys) { - RT_FAIL_IF(coeffs.size() != obs_keys.size(), "number of coefficients should match the number of observables"); - - ostringstream oss; - - for (auto i = 0; i < coeffs.size(); i++) { - if (i > 0) { - if (coeffs[i] > 0) { - oss << " + "; - }else if(coeffs[i] < 0) { - oss << " "; - } - - } - - if (coeffs[i] != 0) - oss << coeffs[i] << "*" << obs_id_type_to_str[obs_keys[i]]; - } + ObsIdType new_id = obs_id_type_to_str.size(); + obs_id_type_to_str[new_id] = oss.str(); + return new_id; + } - ObsIdType new_id = obs_id_type_to_str.size(); - obs_id_type_to_str[new_id] = oss.str(); - return new_id; - } + /** + * @brief Getter function to retrieve the string representation of the observables we created. + */ + string get_obs_str(const ObsIdType &o) { return obs_id_type_to_str.at(o); } - /** - * @brief Getter function to retrieve the string representation of the observables we created. - */ - string get_obs_str(const ObsIdType &o) { - return obs_id_type_to_str.at(o); - } + /** + * @brief This method is used to get the string representation of Sample() and PartialSample() + */ + string get_samples_str(const string &name, DataView &samples, const size_t &shots, + const vector &wires = {}) + { + ostringstream oss; + bool is_first = true; + oss << name << "(" << get_dataview_str(samples); + + if (wires.size() > 0) { + oss << ", wires=" << vector_to_string(wires); + } - /** - * @brief This method is used to get the string representation of Sample() and PartialSample() - */ - string get_samples_str(const string &name, DataView &samples, const size_t &shots, const vector &wires={}) { - ostringstream oss; - bool is_first = true; - oss << name << "(" << get_dataview_str(samples); + oss << ", shots=" << shots << ")"; - if (wires.size() > 0) { - oss << ", wires=" << vector_to_string(wires); - } - - oss << ", shots=" << shots << ")"; + return oss.str(); + } - return oss.str(); + /** + * @brief This method is used to get the string representation of Counts() and PartialCounts(). + */ + string get_counts_str(const string &name, DataView &vals, + DataView &counts, const size_t &shots, + const vector &wires = {}) + { + RT_FAIL_IF(vals.size() != counts.size(), "number of eigenvalues does not matches counts"); + + ostringstream oss; + bool is_first = true; + oss << name << "("; + oss << "["; + auto it1 = vals.begin(); + auto it2 = counts.begin(); + for (; it1 != vals.end(); it1++, it2++) { + if (!is_first) { + oss << ", "; } + // here we build a substring that represents a pair (eigenval, count) + oss << "(" << element_to_str(*it1) << ", " << element_to_str(*it2) << ")"; + is_first = false; + } + oss << "]"; // - /** - * @brief This method is used to get the string representation of Counts() and PartialCounts(). - */ - string get_counts_str(const string &name, DataView &vals, DataView &counts, const size_t &shots, const vector &wires={}) { - RT_FAIL_IF(vals.size() != counts.size(), "number of eigenvalues does not matches counts"); - - ostringstream oss; - bool is_first = true; - oss << name << "("; - oss << "["; - auto it1 = vals.begin(); - auto it2 = counts.begin(); - for ( ; it1 != vals.end(); it1++, it2++) { - if (!is_first) { - oss << ", "; - } - // here we build a substring that represents a pair (eigenval, count) - oss << "(" << element_to_str(*it1) << ", " << element_to_str(*it2) << ")"; - is_first = false; - } - oss << "]"; // - - if (wires.size() > 0) { - oss << ", wires=" << vector_to_string(wires); - } + if (wires.size() > 0) { + oss << ", wires=" << vector_to_string(wires); + } - oss << ", shots=" << shots; + oss << ", shots=" << shots; - oss << ")"; + oss << ")"; - return oss.str(); - } - }; -} + return oss.str(); + } +}; +} // namespace Catalyst::Runtime diff --git a/runtime/lib/backend/null_qubit/NullQubit.hpp b/runtime/lib/backend/null_qubit/NullQubit.hpp index 4fbad68fe7..280a72e21c 100644 --- a/runtime/lib/backend/null_qubit/NullQubit.hpp +++ b/runtime/lib/backend/null_qubit/NullQubit.hpp @@ -16,11 +16,11 @@ #include // generate_n #include +#include #include #include #include #include -#include #include "DataView.hpp" #include "InstructionStrBuilder.hpp" @@ -42,8 +42,10 @@ namespace Catalyst::Runtime::Devices { * of the device; these are used to implement Quantum Instruction Set (QIS) instructions. */ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { - NullQubit(const std::string &kwargs = "{}") { - std::unordered_map device_kwargs = Catalyst::Runtime::parse_kwargs(kwargs); + NullQubit(const std::string &kwargs = "{}") + { + std::unordered_map device_kwargs = + Catalyst::Runtime::parse_kwargs(kwargs); print_instructions = device_kwargs["print_instructions"] == "True" ? 1 : 0; } ~NullQubit() = default; // LCOV_EXCL_LINE @@ -61,7 +63,8 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { auto AllocateQubit() -> QubitIdType { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("AllocateQubit", "") << std::endl; + std::cout << instruction_str_builder.get_simple_op_str("AllocateQubit", "") + << std::endl; } num_qubits_++; // next_id @@ -76,12 +79,14 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * @return `std::vector` */ auto AllocateQubits(size_t num_qubits) -> std::vector - { + { bool prev_print_instructions = print_instructions; if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("AllocateQubits", num_qubits) << std::endl; + std::cout << instruction_str_builder.get_simple_op_str("AllocateQubits", num_qubits) + << std::endl; } - print_instructions = false; // set to false so we do not print instructions for each call to AllocateQubit() below. + print_instructions = false; // set to false so we do not print instructions for each call to + // AllocateQubit() below. if (!num_qubits) { return {}; @@ -114,7 +119,8 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void ReleaseAllQubits() { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("ReleaseAllQubits", "") << std::endl; + std::cout << instruction_str_builder.get_simple_op_str("ReleaseAllQubits", "") + << std::endl; } num_qubits_ = 0; @@ -215,7 +221,9 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { const std::vector &controlled_values = {}) { if (print_instructions) { - std::cout << instruction_str_builder.get_named_op_str(name, params, wires, inverse, controlled_wires, controlled_values) << std::endl; + std::cout << instruction_str_builder.get_named_op_str( + name, params, wires, inverse, controlled_wires, controlled_values) + << std::endl; } } @@ -229,7 +237,9 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { const std::vector &controlled_values = {}) { if (print_instructions) { - std::cout << instruction_str_builder.get_matrix_op_str(matrix, wires, inverse, controlled_wires, controlled_values) << std::endl; + std::cout << instruction_str_builder.get_matrix_op_str( + matrix, wires, inverse, controlled_wires, controlled_values) + << std::endl; } } @@ -242,7 +252,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { auto Observable(ObsId obs_id, const std::vector> &matrix, const std::vector &wires) -> ObsIdType { - return instruction_str_builder.create_obs_str(obs_id, matrix, wires); + return instruction_str_builder.create_obs_str(obs_id, matrix, wires); } /** @@ -250,7 +260,8 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * * @return `ObsIdType` Index of the constructed observable */ - auto TensorObservable(const std::vector &obs_keys) -> ObsIdType { + auto TensorObservable(const std::vector &obs_keys) -> ObsIdType + { return instruction_str_builder.create_tensor_obs_str(obs_keys); } @@ -259,8 +270,8 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * * @return `ObsIdType` Index of the constructed observable */ - auto HamiltonianObservable(const std::vector &matrix, const std::vector &obs_keys) - -> ObsIdType + auto HamiltonianObservable(const std::vector &matrix, + const std::vector &obs_keys) -> ObsIdType { return instruction_str_builder.create_hamiltonian_obs_str(matrix, obs_keys); } @@ -270,11 +281,12 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * * @return `double` The expected value */ - auto Expval(ObsIdType o) -> double { + auto Expval(ObsIdType o) -> double + { if (print_instructions) { std::cout << instruction_str_builder.get_simple_op_str("Expval", o) << std::endl; } - return 0.0; + return 0.0; } /** @@ -282,17 +294,19 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * * @return `double` The variance */ - auto Var(ObsIdType o) -> double { + auto Var(ObsIdType o) -> double + { if (print_instructions) { std::cout << instruction_str_builder.get_simple_op_str("Var", o) << std::endl; } - return 0.0; + return 0.0; } /** * @brief Doesn't Get the state-vector of a device. */ - void State(DataView, 1> &state) { + void State(DataView, 1> &state) + { if (print_instructions) { std::cout << instruction_str_builder.get_simple_op_str("State", state) << std::endl; } @@ -301,7 +315,8 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { /** * @brief Doesn't Compute the probabilities of each computational basis state. */ - void Probs(DataView &probs) { + void Probs(DataView &probs) + { if (print_instructions) { std::cout << instruction_str_builder.get_simple_op_str("Probs", probs) << std::endl; } @@ -310,9 +325,11 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { /** * @brief Doesn't Compute the probabilities for a subset of the full system. */ - void PartialProbs(DataView &probs, const std::vector &wires) { + void PartialProbs(DataView &probs, const std::vector &wires) + { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("PartialProbs", probs) << std::endl; + std::cout << instruction_str_builder.get_simple_op_str("PartialProbs", probs) + << std::endl; } } @@ -320,9 +337,11 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * @brief Doesn't Compute samples with the number of shots on the entire wires, * returing raw samples. */ - void Sample(DataView &samples, size_t shots) { + void Sample(DataView &samples, size_t shots) + { if (print_instructions) { - std::cout << instruction_str_builder.get_samples_str("Sample", samples, shots) << std::endl; + std::cout << instruction_str_builder.get_samples_str("Sample", samples, shots) + << std::endl; } } @@ -334,20 +353,25 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * shape `shots * numWires`. The built-in iterator in `DataView` * iterates over all elements of `samples` row-wise. */ - void PartialSample(DataView &samples, const std::vector &wires, size_t shots) { + void PartialSample(DataView &samples, const std::vector &wires, + size_t shots) + { if (print_instructions) { - std::cout << instruction_str_builder.get_samples_str("PartialSample", samples, shots, wires) << std::endl; + std::cout << instruction_str_builder.get_samples_str("PartialSample", samples, shots, + wires) + << std::endl; } - } /** * @brief Doesn't Sample with the number of shots on the entire wires, returning the * number of counts for each sample. */ - void Counts(DataView &eigen_vals, DataView &counts, size_t shots) { + void Counts(DataView &eigen_vals, DataView &counts, size_t shots) + { if (print_instructions) { - std::cout << instruction_str_builder.get_counts_str("Counts", eigen_vals, counts, shots) << std::endl; + std::cout << instruction_str_builder.get_counts_str("Counts", eigen_vals, counts, shots) + << std::endl; } } @@ -359,7 +383,9 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { const std::vector &wires, size_t shots) { if (print_instructions) { - std::cout << instruction_str_builder.get_counts_str("PartialCounts", eigen_vals, counts, shots, wires) << std::endl; + std::cout << instruction_str_builder.get_counts_str("PartialCounts", eigen_vals, counts, + shots, wires) + << std::endl; } } From 27dd5cfaf93cba39119f3c8a50398b3946757a85 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Fri, 29 Nov 2024 20:17:31 +0100 Subject: [PATCH 08/28] fix: printing bugs --- .../null_qubit/InstructionStrBuilder.hpp | 24 ++++++++++++------- runtime/lib/backend/null_qubit/NullQubit.hpp | 2 +- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 6fcdc2bd14..f8d5782561 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -130,10 +130,14 @@ class InstructionStrBuilder { * PartialProbs() */ template - string get_simple_op_str(const string &name, DataView &dataview) + string get_simple_op_str(const string &name, DataView &dataview, const std::vector &wires={}) { ostringstream oss; - oss << name << "(" << get_dataview_str(dataview) << ")"; + oss << name << "(" << get_dataview_str(dataview); + if (wires.size() > 0) { + oss << ", wires=" << vector_to_string(wires); + } + oss << ")"; return oss.str(); } @@ -168,7 +172,7 @@ class InstructionStrBuilder { // if inverse is false, we will not print its value if (inverse) - values_to_print.push_back("inverse=True"); + values_to_print.push_back("inverse=true"); if (controlled_wires.size() > 0) values_to_print.push_back("control=" + vector_to_string(controlled_wires)); @@ -191,7 +195,7 @@ class InstructionStrBuilder { * @brief This method is used to get the string representation of MatrixOperation() */ string get_matrix_op_str(const std::vector> &matrix, - const std::vector &wires, bool inverse, + const std::vector &wires, bool inverse=false, const std::vector &controlled_wires = {}, const std::vector &controlled_values = {}, const string &name = "MatrixOperation") @@ -203,10 +207,11 @@ class InstructionStrBuilder { values_to_print.emplace_back(vector_to_string(matrix)); - values_to_print.emplace_back("wires=" + vector_to_string(wires)); + if (wires.size() > 0) + values_to_print.emplace_back("wires=" + vector_to_string(wires)); if (inverse) - values_to_print.push_back("inverse=True"); + values_to_print.push_back("inverse=true"); if (controlled_wires.size() > 0) values_to_print.push_back("control=" + vector_to_string(controlled_wires)); @@ -285,8 +290,9 @@ class InstructionStrBuilder { ostringstream oss; + bool is_first = true; for (auto i = 0; i < coeffs.size(); i++) { - if (i > 0) { + if (!is_first) { if (coeffs[i] > 0) { oss << " + "; } @@ -295,8 +301,10 @@ class InstructionStrBuilder { } } - if (coeffs[i] != 0) + if (coeffs[i] != 0) { oss << coeffs[i] << "*" << obs_id_type_to_str[obs_keys[i]]; + is_first = false; + } } ObsIdType new_id = obs_id_type_to_str.size(); diff --git a/runtime/lib/backend/null_qubit/NullQubit.hpp b/runtime/lib/backend/null_qubit/NullQubit.hpp index 280a72e21c..1c2da20247 100644 --- a/runtime/lib/backend/null_qubit/NullQubit.hpp +++ b/runtime/lib/backend/null_qubit/NullQubit.hpp @@ -328,7 +328,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void PartialProbs(DataView &probs, const std::vector &wires) { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("PartialProbs", probs) + std::cout << instruction_str_builder.get_simple_op_str("PartialProbs", probs, wires) << std::endl; } } From a53e314c075a0a55c225a0ecd99a7107b50d63f6 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Fri, 29 Nov 2024 20:18:46 +0100 Subject: [PATCH 09/28] feat: tests --- runtime/tests/CMakeLists.txt | 1 + runtime/tests/Test_InstructionStrBuilder.cpp | 98 ++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 runtime/tests/Test_InstructionStrBuilder.cpp diff --git a/runtime/tests/CMakeLists.txt b/runtime/tests/CMakeLists.txt index 9924ea26af..75155e5163 100644 --- a/runtime/tests/CMakeLists.txt +++ b/runtime/tests/CMakeLists.txt @@ -36,6 +36,7 @@ target_link_libraries(runner_tests_qir_runtime PRIVATE target_sources(runner_tests_qir_runtime PRIVATE Test_NullQubit.cpp + Test_InstructionStrBuilder.cpp ) catch_discover_tests(runner_tests_qir_runtime) diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp new file mode 100644 index 0000000000..5a76c7bb32 --- /dev/null +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -0,0 +1,98 @@ +#include "InstructionStrBuilder.hpp" +#include "TestUtils.hpp" + +TEST_CASE("string building is correct for instructions that require one parameter", "[InstructionStrBuilder]") { + InstructionStrBuilder str_builder; + CHECK(str_builder.get_simple_op_str("AllocateQubit", "") == "AllocateQubit()"); + CHECK(str_builder.get_simple_op_str("AllocateQubits", 5) == "AllocateQubits(5)"); + CHECK(str_builder.get_simple_op_str("ReleaseQubit", "") == "ReleaseQubit()"); + CHECK(str_builder.get_simple_op_str("ReleaseAllQubits", "") == "ReleaseAllQubits()"); + CHECK(str_builder.get_simple_op_str("Measure", 0) == "Measure(0)"); +} + +TEST_CASE("string building is correct for instructions that require a parameter of type ObsIdType", "[InstructionStrBuilder]") { + InstructionStrBuilder str_builder; + auto id = str_builder.create_obs_str(ObsId::PauliX, {}, {0}); + + CHECK(str_builder.get_simple_op_str("Expval", id) == "Expval(PauliX(0))"); + CHECK(str_builder.get_simple_op_str("Var", id) == "Var(PauliX(0))"); +} + +TEST_CASE("string building is correct for instructions that require a parameter of type DataView", "[InstructionStrBuilder]") { + InstructionStrBuilder str_builder; + std::vector> state(2); + DataView, 1> view(state); + + CHECK(str_builder.get_simple_op_str("State", view) == "State([0, 0])"); + CHECK(str_builder.get_simple_op_str("Probs", view) == "Probs([0, 0])"); + CHECK(str_builder.get_simple_op_str("PartialProbs", view, {0}) == "PartialProbs([0, 0], wires=[0])"); +} + +TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuilder]") { + InstructionStrBuilder str_builder; + + CHECK(str_builder.get_named_op_str("NamedOperation", {3.14, 0.705}, {}) == "NamedOperation(3.140000, 0.705000)"); + CHECK(str_builder.get_named_op_str("NamedOperation", {3.14, 0.705}, {0}) == "NamedOperation(3.140000, 0.705000, wires=[0])"); + CHECK(str_builder.get_named_op_str("NamedOperation", {}, {0}) == "NamedOperation(0)"); + CHECK(str_builder.get_named_op_str("NamedOperation", {}, {0}, true) == "NamedOperation(0, inverse=true)"); + CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}) == "NamedOperation(1, control=[0])"); + CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}, {true}) == "NamedOperation(1, control=[0], control_value=[1])"); +} + +TEST_CASE("string building is correct for MatrixOperation", "[InstructionStrBuilder]") { + InstructionStrBuilder str_builder; + std::vector> v = {std::complex(1.0), std::complex(0.0), std::complex(0.0), std::complex(1.0)}; + + CHECK(str_builder.get_matrix_op_str(v, {}) == "MatrixOperation([1, 0, 0, 1])"); + CHECK(str_builder.get_matrix_op_str(v, {0}) == "MatrixOperation([1, 0, 0, 1], wires=[0])"); + CHECK(str_builder.get_matrix_op_str(v, {0}, true) == "MatrixOperation([1, 0, 0, 1], wires=[0], inverse=true)"); + CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}) == "MatrixOperation([1, 0, 0, 1], wires=[1], control=[0])"); + CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}, {true}) == "MatrixOperation([1, 0, 0, 1], wires=[1], control=[0], control_value=[1])"); +} + +TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") { + InstructionStrBuilder str_builder; + + ObsId obs_id1 = ObsId::PauliX; + ObsId obs_id2 = ObsId::Hermitian; + + CHECK(str_builder.create_obs_str(obs_id1, {}, {0}) == 0); + CHECK(str_builder.create_obs_str(obs_id2, {1, 0, 0, 1}, {0}) == 1); + CHECK(str_builder.get_obs_str(0) == "PauliX(0)"); + CHECK(str_builder.get_obs_str(1) == "Hermitian([1, 0, 0, 1], wires=[0])"); + + // test tensor product observable + CHECK(str_builder.create_tensor_obs_str({0, 1}) == 2); + CHECK(str_builder.get_obs_str(2) == "PauliX(0) ⊗ Hermitian([1, 0, 0, 1], wires=[0])"); + + // test hamiltonian observable + CHECK(str_builder.create_hamiltonian_obs_str({0.1, 0.2}, {0, 1}) == 3); + CHECK(str_builder.get_obs_str(3) == "0.1*PauliX(0) + 0.2*Hermitian([1, 0, 0, 1], wires=[0])"); + + CHECK(str_builder.create_hamiltonian_obs_str({0.0, 0.2}, {0, 1}) == 4); + CHECK(str_builder.get_obs_str(4) == "0.2*Hermitian([1, 0, 0, 1], wires=[0])"); + + CHECK(str_builder.create_hamiltonian_obs_str({0.1, -0.2}, {0, 1}) == 5); + CHECK(str_builder.get_obs_str(5) == "0.1*PauliX(0) -0.2*Hermitian([1, 0, 0, 1], wires=[0])"); +} + +// TEST_CASE("string building for Sample() and PartialSample()", "[InstructionStrBuilder]") { +// InstructionStrBuilder str_builder; +// std::vector> v(2); +// DataView>, 2> view({v, v}, 0, {2, 2}, {1,1}); + +// CHECK(str_builder.get_samples_str("Sample", view, 100) == "Sample([0, 0], shots=100)"); +// CHECK(str_builder.get_samples_str("PartialSample", view, 100, {0}) == "PartialSample([0, 0], wires=[0], shots=100)"); +// } + +TEST_CASE("string building for Counts() and PartialCounts()", "[InstructionStrBuilder]") { + InstructionStrBuilder str_builder; + std::vector state(2); + DataView state_view(state); + + std::vector counts(2); + DataView counts_view(counts); + + CHECK(str_builder.get_counts_str("Counts", state_view, counts_view, 100) == "Counts([(0.000000, 0), (0.000000, 0)], shots=100)"); + CHECK(str_builder.get_counts_str("PartialCounts", state_view, counts_view, 100, {0}) == "PartialCounts([(0.000000, 0), (0.000000, 0)], wires=[0], shots=100)"); +} \ No newline at end of file From 84be47979d5e1dc243bdac2d231d6c922c613c20 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Fri, 29 Nov 2024 20:22:55 +0100 Subject: [PATCH 10/28] refact: make format --- .../null_qubit/InstructionStrBuilder.hpp | 9 +-- runtime/tests/Test_InstructionStrBuilder.cpp | 63 +++++++++++++------ 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index f8d5782561..3c36a686a9 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -130,10 +130,11 @@ class InstructionStrBuilder { * PartialProbs() */ template - string get_simple_op_str(const string &name, DataView &dataview, const std::vector &wires={}) + string get_simple_op_str(const string &name, DataView &dataview, + const std::vector &wires = {}) { ostringstream oss; - oss << name << "(" << get_dataview_str(dataview); + oss << name << "(" << get_dataview_str(dataview); if (wires.size() > 0) { oss << ", wires=" << vector_to_string(wires); } @@ -195,7 +196,7 @@ class InstructionStrBuilder { * @brief This method is used to get the string representation of MatrixOperation() */ string get_matrix_op_str(const std::vector> &matrix, - const std::vector &wires, bool inverse=false, + const std::vector &wires, bool inverse = false, const std::vector &controlled_wires = {}, const std::vector &controlled_values = {}, const string &name = "MatrixOperation") @@ -304,7 +305,7 @@ class InstructionStrBuilder { if (coeffs[i] != 0) { oss << coeffs[i] << "*" << obs_id_type_to_str[obs_keys[i]]; is_first = false; - } + } } ObsIdType new_id = obs_id_type_to_str.size(); diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index 5a76c7bb32..e7a6ac5f68 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -1,7 +1,9 @@ #include "InstructionStrBuilder.hpp" #include "TestUtils.hpp" -TEST_CASE("string building is correct for instructions that require one parameter", "[InstructionStrBuilder]") { +TEST_CASE("string building is correct for instructions that require one parameter", + "[InstructionStrBuilder]") +{ InstructionStrBuilder str_builder; CHECK(str_builder.get_simple_op_str("AllocateQubit", "") == "AllocateQubit()"); CHECK(str_builder.get_simple_op_str("AllocateQubits", 5) == "AllocateQubits(5)"); @@ -10,7 +12,9 @@ TEST_CASE("string building is correct for instructions that require one paramete CHECK(str_builder.get_simple_op_str("Measure", 0) == "Measure(0)"); } -TEST_CASE("string building is correct for instructions that require a parameter of type ObsIdType", "[InstructionStrBuilder]") { +TEST_CASE("string building is correct for instructions that require a parameter of type ObsIdType", + "[InstructionStrBuilder]") +{ InstructionStrBuilder str_builder; auto id = str_builder.create_obs_str(ObsId::PauliX, {}, {0}); @@ -18,39 +22,54 @@ TEST_CASE("string building is correct for instructions that require a parameter CHECK(str_builder.get_simple_op_str("Var", id) == "Var(PauliX(0))"); } -TEST_CASE("string building is correct for instructions that require a parameter of type DataView", "[InstructionStrBuilder]") { +TEST_CASE("string building is correct for instructions that require a parameter of type DataView", + "[InstructionStrBuilder]") +{ InstructionStrBuilder str_builder; std::vector> state(2); DataView, 1> view(state); CHECK(str_builder.get_simple_op_str("State", view) == "State([0, 0])"); CHECK(str_builder.get_simple_op_str("Probs", view) == "Probs([0, 0])"); - CHECK(str_builder.get_simple_op_str("PartialProbs", view, {0}) == "PartialProbs([0, 0], wires=[0])"); + CHECK(str_builder.get_simple_op_str("PartialProbs", view, {0}) == + "PartialProbs([0, 0], wires=[0])"); } -TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuilder]") { +TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuilder]") +{ InstructionStrBuilder str_builder; - CHECK(str_builder.get_named_op_str("NamedOperation", {3.14, 0.705}, {}) == "NamedOperation(3.140000, 0.705000)"); - CHECK(str_builder.get_named_op_str("NamedOperation", {3.14, 0.705}, {0}) == "NamedOperation(3.140000, 0.705000, wires=[0])"); + CHECK(str_builder.get_named_op_str("NamedOperation", {3.14, 0.705}, {}) == + "NamedOperation(3.140000, 0.705000)"); + CHECK(str_builder.get_named_op_str("NamedOperation", {3.14, 0.705}, {0}) == + "NamedOperation(3.140000, 0.705000, wires=[0])"); CHECK(str_builder.get_named_op_str("NamedOperation", {}, {0}) == "NamedOperation(0)"); - CHECK(str_builder.get_named_op_str("NamedOperation", {}, {0}, true) == "NamedOperation(0, inverse=true)"); - CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}) == "NamedOperation(1, control=[0])"); - CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}, {true}) == "NamedOperation(1, control=[0], control_value=[1])"); + CHECK(str_builder.get_named_op_str("NamedOperation", {}, {0}, true) == + "NamedOperation(0, inverse=true)"); + CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}) == + "NamedOperation(1, control=[0])"); + CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}, {true}) == + "NamedOperation(1, control=[0], control_value=[1])"); } -TEST_CASE("string building is correct for MatrixOperation", "[InstructionStrBuilder]") { +TEST_CASE("string building is correct for MatrixOperation", "[InstructionStrBuilder]") +{ InstructionStrBuilder str_builder; - std::vector> v = {std::complex(1.0), std::complex(0.0), std::complex(0.0), std::complex(1.0)}; + std::vector> v = {std::complex(1.0), std::complex(0.0), + std::complex(0.0), std::complex(1.0)}; CHECK(str_builder.get_matrix_op_str(v, {}) == "MatrixOperation([1, 0, 0, 1])"); CHECK(str_builder.get_matrix_op_str(v, {0}) == "MatrixOperation([1, 0, 0, 1], wires=[0])"); - CHECK(str_builder.get_matrix_op_str(v, {0}, true) == "MatrixOperation([1, 0, 0, 1], wires=[0], inverse=true)"); - CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}) == "MatrixOperation([1, 0, 0, 1], wires=[1], control=[0])"); - CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}, {true}) == "MatrixOperation([1, 0, 0, 1], wires=[1], control=[0], control_value=[1])"); + CHECK(str_builder.get_matrix_op_str(v, {0}, true) == + "MatrixOperation([1, 0, 0, 1], wires=[0], inverse=true)"); + CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}) == + "MatrixOperation([1, 0, 0, 1], wires=[1], control=[0])"); + CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}, {true}) == + "MatrixOperation([1, 0, 0, 1], wires=[1], control=[0], control_value=[1])"); } -TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") { +TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") +{ InstructionStrBuilder str_builder; ObsId obs_id1 = ObsId::PauliX; @@ -82,10 +101,12 @@ TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") { // DataView>, 2> view({v, v}, 0, {2, 2}, {1,1}); // CHECK(str_builder.get_samples_str("Sample", view, 100) == "Sample([0, 0], shots=100)"); -// CHECK(str_builder.get_samples_str("PartialSample", view, 100, {0}) == "PartialSample([0, 0], wires=[0], shots=100)"); +// CHECK(str_builder.get_samples_str("PartialSample", view, 100, {0}) == "PartialSample([0, 0], +// wires=[0], shots=100)"); // } -TEST_CASE("string building for Counts() and PartialCounts()", "[InstructionStrBuilder]") { +TEST_CASE("string building for Counts() and PartialCounts()", "[InstructionStrBuilder]") +{ InstructionStrBuilder str_builder; std::vector state(2); DataView state_view(state); @@ -93,6 +114,8 @@ TEST_CASE("string building for Counts() and PartialCounts()", "[InstructionStrBu std::vector counts(2); DataView counts_view(counts); - CHECK(str_builder.get_counts_str("Counts", state_view, counts_view, 100) == "Counts([(0.000000, 0), (0.000000, 0)], shots=100)"); - CHECK(str_builder.get_counts_str("PartialCounts", state_view, counts_view, 100, {0}) == "PartialCounts([(0.000000, 0), (0.000000, 0)], wires=[0], shots=100)"); + CHECK(str_builder.get_counts_str("Counts", state_view, counts_view, 100) == + "Counts([(0.000000, 0), (0.000000, 0)], shots=100)"); + CHECK(str_builder.get_counts_str("PartialCounts", state_view, counts_view, 100, {0}) == + "PartialCounts([(0.000000, 0), (0.000000, 0)], wires=[0], shots=100)"); } \ No newline at end of file From 32cbf9218ee168915fa7901e723b38a0bc90253e Mon Sep 17 00:00:00 2001 From: smml1996 Date: Fri, 29 Nov 2024 20:25:12 +0100 Subject: [PATCH 11/28] refact: removes commented test --- runtime/tests/Test_InstructionStrBuilder.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index e7a6ac5f68..9e2c70b71e 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -95,16 +95,6 @@ TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") CHECK(str_builder.get_obs_str(5) == "0.1*PauliX(0) -0.2*Hermitian([1, 0, 0, 1], wires=[0])"); } -// TEST_CASE("string building for Sample() and PartialSample()", "[InstructionStrBuilder]") { -// InstructionStrBuilder str_builder; -// std::vector> v(2); -// DataView>, 2> view({v, v}, 0, {2, 2}, {1,1}); - -// CHECK(str_builder.get_samples_str("Sample", view, 100) == "Sample([0, 0], shots=100)"); -// CHECK(str_builder.get_samples_str("PartialSample", view, 100, {0}) == "PartialSample([0, 0], -// wires=[0], shots=100)"); -// } - TEST_CASE("string building for Counts() and PartialCounts()", "[InstructionStrBuilder]") { InstructionStrBuilder str_builder; From 75d33c091eaed06717aef2c47f712b9f2908addc Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 10:37:21 +0100 Subject: [PATCH 12/28] adds missing conventional final line to Test_InstructionStrBuilder.cpp Co-authored-by: David Ittah --- runtime/tests/Test_InstructionStrBuilder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index 9e2c70b71e..057c0738cf 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -108,4 +108,4 @@ TEST_CASE("string building for Counts() and PartialCounts()", "[InstructionStrBu "Counts([(0.000000, 0), (0.000000, 0)], shots=100)"); CHECK(str_builder.get_counts_str("PartialCounts", state_view, counts_view, 100, {0}) == "PartialCounts([(0.000000, 0), (0.000000, 0)], wires=[0], shots=100)"); -} \ No newline at end of file +} From 32bcb4e1b78138a3673f71d5f03fab22cfafe7cb Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:00:31 +0100 Subject: [PATCH 13/28] fix: codefactor-io too long lines --- frontend/catalyst/device/qjit_device.py | 6 +++--- frontend/catalyst/jit.py | 6 ++++-- frontend/catalyst/qfunc.py | 3 ++- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/frontend/catalyst/device/qjit_device.py b/frontend/catalyst/device/qjit_device.py index 6487874f85..225d56ce1f 100644 --- a/frontend/catalyst/device/qjit_device.py +++ b/frontend/catalyst/device/qjit_device.py @@ -307,9 +307,9 @@ def __init__(self, original_device, print_instructions=False): self.backend_name = backend.c_interface_name self.backend_lib = backend.lpath self.backend_kwargs = backend.kwargs - self.backend_kwargs["print_instructions"] = ( - print_instructions # include 'print_instructions' as a keyword argument for the device constructor. - ) + + # include 'print_instructions' as a keyword argument for the device constructor. + self.backend_kwargs["print_instructions"] = print_instructions self.capabilities = get_qjit_device_capabilities(original_device_capabilities) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 63c7089e15..46c1c79c88 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -156,7 +156,8 @@ def qjit( dictionaries of valid keyword arguments and values for the specific pass. The order of keys in this dictionary will determine the pass pipeline. If not specified, the default pass pipeline will be applied. - print_instructions (Optional[bool]): If set to True, instructions are printed when executing in the null device. + print_instructions (Optional[bool]): + If set to True, instructions are printed when executing in the null device. Returns: QJIT object. @@ -457,7 +458,8 @@ class QJIT(CatalystCallable): Args: fn (Callable): the quantum or classical function to compile compile_options (CompileOptions): compilation options to use - print_instructions (Optional[bool]): if set to True, instructions are printed when executing in the null device. + print_instructions (Optional[bool]): If True, prints instructions + when executing in a null device. :ivar original_function: This attribute stores `fn`, the quantum or classical function object to compile, as is, without any modifications diff --git a/frontend/catalyst/qfunc.py b/frontend/catalyst/qfunc.py index 60d90b2846..c2a4d9653d 100644 --- a/frontend/catalyst/qfunc.py +++ b/frontend/catalyst/qfunc.py @@ -122,7 +122,8 @@ def __call__(self, *args, **kwargs): mcm_config.postselect_mode = mcm_config.postselect_mode or "hw-like" return Function(dynamic_one_shot(self, mcm_config=mcm_config))(*args, **kwargs) - # retrieve flag of whether or not to print instructions (in case we execute the pre-compiled program in the null device) + # retrieve the flag to print instructions, used for executing + # pre-compiled programs in a null device. print_instructions = kwargs.pop("print_instructions", False) qjit_device = QJITDevice(self.device, print_instructions) From d998f46e8ff5314bac8553c2b6bc9ebfbdab3403 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:10:30 +0100 Subject: [PATCH 14/28] fix: namedOperation prints always wires=[..] --- .../backend/null_qubit/InstructionStrBuilder.hpp | 14 +++++--------- runtime/tests/Test_InstructionStrBuilder.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 3c36a686a9..4c31f3ba99 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -148,7 +148,7 @@ class InstructionStrBuilder { string get_named_op_str(const std::string &name, const std::vector ¶ms, const std::vector &wires, bool inverse = false, const std::vector &controlled_wires = {}, - const std::vector &controlled_values = {}) + const std::vector &controlled_values = {}, const bool &explicit_wires=true) { ostringstream oss; vector values_to_print; // Store the string representation of the parameters passed @@ -159,16 +159,12 @@ class InstructionStrBuilder { values_to_print.push_back(std::to_string(p)); if (wires.size() > 0) { - if (params.size()) { - // when an operation corresponds to a parametric gate, explicity specify the wires - // as a single vector. E.g Rx(3.14, wires=[0]) + if (explicit_wires) { values_to_print.push_back("wires=" + vector_to_string(wires, true)); - } - else { - // Otherwise, we do not represent it as a vector but rather as a list separated with - // commas. E.g. CX(0,1) + } else { values_to_print.push_back(vector_to_string(wires, false)); } + } // if inverse is false, we will not print its value @@ -248,7 +244,7 @@ class InstructionStrBuilder { else { auto it = obs_id_to_str.find(obs_id); if (it != obs_id_to_str.end()) { - obs_id_type_to_str.emplace(new_id, get_named_op_str(it->second, {}, wires)); + obs_id_type_to_str.emplace(new_id, get_named_op_str(it->second, {}, wires, false, {}, {}, false)); } else { RT_FAIL( diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index 9e2c70b71e..bfd5c58fc7 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -43,13 +43,13 @@ TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuild "NamedOperation(3.140000, 0.705000)"); CHECK(str_builder.get_named_op_str("NamedOperation", {3.14, 0.705}, {0}) == "NamedOperation(3.140000, 0.705000, wires=[0])"); - CHECK(str_builder.get_named_op_str("NamedOperation", {}, {0}) == "NamedOperation(0)"); + CHECK(str_builder.get_named_op_str("NamedOperation", {}, {0}) == "NamedOperation(wires=[0])"); CHECK(str_builder.get_named_op_str("NamedOperation", {}, {0}, true) == - "NamedOperation(0, inverse=true)"); + "NamedOperation(wires=[0], inverse=true)"); CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}) == - "NamedOperation(1, control=[0])"); + "NamedOperation(wires=[1], control=[0])"); CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}, {true}) == - "NamedOperation(1, control=[0], control_value=[1])"); + "NamedOperation(wires=[1], control=[0], control_value=[1])"); } TEST_CASE("string building is correct for MatrixOperation", "[InstructionStrBuilder]") From afea6e3ec97e0f6b87903238035d2e7805ae68d6 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:17:26 +0100 Subject: [PATCH 15/28] include a non-trivial complex number for better coverage in MatrixOperation test --- .../lib/backend/null_qubit/InstructionStrBuilder.hpp | 7 ++++++- runtime/tests/Test_InstructionStrBuilder.cpp | 12 ++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 4c31f3ba99..4b12fb9c90 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -49,7 +49,12 @@ class InstructionStrBuilder { if (c.imag() != 0) { // to keep printing output as short as possible, we only print the imaginary part // whenever is different from zero; - oss << " + " << c.imag() << "i"; + if (c.imag() > 0) { + oss << " + " << c.imag() << "i"; + } else { + oss << " - " << -1*c.imag() << "i"; + } + } return oss.str(); } diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index bfd5c58fc7..b876ef0942 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -55,17 +55,17 @@ TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuild TEST_CASE("string building is correct for MatrixOperation", "[InstructionStrBuilder]") { InstructionStrBuilder str_builder; - std::vector> v = {std::complex(1.0), std::complex(0.0), + std::vector> v = {std::complex(0.707, -0.707), std::complex(0.0), std::complex(0.0), std::complex(1.0)}; - CHECK(str_builder.get_matrix_op_str(v, {}) == "MatrixOperation([1, 0, 0, 1])"); - CHECK(str_builder.get_matrix_op_str(v, {0}) == "MatrixOperation([1, 0, 0, 1], wires=[0])"); + CHECK(str_builder.get_matrix_op_str(v, {}) == "MatrixOperation([0.707 - 0.707i, 0, 0, 1])"); + CHECK(str_builder.get_matrix_op_str(v, {0}) == "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[0])"); CHECK(str_builder.get_matrix_op_str(v, {0}, true) == - "MatrixOperation([1, 0, 0, 1], wires=[0], inverse=true)"); + "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[0], inverse=true)"); CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}) == - "MatrixOperation([1, 0, 0, 1], wires=[1], control=[0])"); + "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[1], control=[0])"); CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}, {true}) == - "MatrixOperation([1, 0, 0, 1], wires=[1], control=[0], control_value=[1])"); + "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[1], control=[0], control_value=[1])"); } TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") From f04edc772d031a21a75b02425b3cf4cf973433ae Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:26:00 +0100 Subject: [PATCH 16/28] fix: spacing of negative coefficients --- runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp | 9 ++++++--- runtime/tests/Test_InstructionStrBuilder.cpp | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 4b12fb9c90..5598969230 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -295,16 +295,19 @@ class InstructionStrBuilder { bool is_first = true; for (auto i = 0; i < coeffs.size(); i++) { if (!is_first) { + + // handle the addition of the terms if (coeffs[i] > 0) { - oss << " + "; + oss << " + " << coeffs[i]; } else if (coeffs[i] < 0) { - oss << " "; + oss << " - " << -1*coeffs[i]; // a negative sign is manually added so we multiply the coefficient by -1 } } if (coeffs[i] != 0) { - oss << coeffs[i] << "*" << obs_id_type_to_str[obs_keys[i]]; + if (is_first) oss << coeffs[i]; // if is the first element then this coefficient is not yet added to the string + oss << "*" << obs_id_type_to_str[obs_keys[i]]; is_first = false; } } diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index b876ef0942..6756ab890a 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -92,7 +92,7 @@ TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") CHECK(str_builder.get_obs_str(4) == "0.2*Hermitian([1, 0, 0, 1], wires=[0])"); CHECK(str_builder.create_hamiltonian_obs_str({0.1, -0.2}, {0, 1}) == 5); - CHECK(str_builder.get_obs_str(5) == "0.1*PauliX(0) -0.2*Hermitian([1, 0, 0, 1], wires=[0])"); + CHECK(str_builder.get_obs_str(5) == "0.1*PauliX(0) - 0.2*Hermitian([1, 0, 0, 1], wires=[0])"); } TEST_CASE("string building for Counts() and PartialCounts()", "[InstructionStrBuilder]") From efdc884481859527e76dc367b93856ada299c064 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:30:28 +0100 Subject: [PATCH 17/28] refact: State() does not prints view --- runtime/lib/backend/null_qubit/NullQubit.hpp | 2 +- runtime/tests/Test_InstructionStrBuilder.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/lib/backend/null_qubit/NullQubit.hpp b/runtime/lib/backend/null_qubit/NullQubit.hpp index 1c2da20247..b3611f728c 100644 --- a/runtime/lib/backend/null_qubit/NullQubit.hpp +++ b/runtime/lib/backend/null_qubit/NullQubit.hpp @@ -308,7 +308,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void State(DataView, 1> &state) { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("State", state) << std::endl; + std::cout << instruction_str_builder.get_simple_op_str("State", "") << std::endl; } } diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index 6756ab890a..d632ecad42 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -1,7 +1,7 @@ #include "InstructionStrBuilder.hpp" #include "TestUtils.hpp" -TEST_CASE("string building is correct for instructions that require one parameter", +TEST_CASE("string building is correct for instructions that require up to one parameter ", "[InstructionStrBuilder]") { InstructionStrBuilder str_builder; @@ -9,6 +9,7 @@ TEST_CASE("string building is correct for instructions that require one paramete CHECK(str_builder.get_simple_op_str("AllocateQubits", 5) == "AllocateQubits(5)"); CHECK(str_builder.get_simple_op_str("ReleaseQubit", "") == "ReleaseQubit()"); CHECK(str_builder.get_simple_op_str("ReleaseAllQubits", "") == "ReleaseAllQubits()"); + CHECK(str_builder.get_simple_op_str("State", "") == "State()"); CHECK(str_builder.get_simple_op_str("Measure", 0) == "Measure(0)"); } @@ -29,7 +30,6 @@ TEST_CASE("string building is correct for instructions that require a parameter std::vector> state(2); DataView, 1> view(state); - CHECK(str_builder.get_simple_op_str("State", view) == "State([0, 0])"); CHECK(str_builder.get_simple_op_str("Probs", view) == "Probs([0, 0])"); CHECK(str_builder.get_simple_op_str("PartialProbs", view, {0}) == "PartialProbs([0, 0], wires=[0])"); From f0811305914ec4b76ed3cc0ae409672b89583eea Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:33:26 +0100 Subject: [PATCH 18/28] fix: only create observables string when print_instructions=true --- runtime/lib/backend/null_qubit/NullQubit.hpp | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/runtime/lib/backend/null_qubit/NullQubit.hpp b/runtime/lib/backend/null_qubit/NullQubit.hpp index b3611f728c..1e0c536398 100644 --- a/runtime/lib/backend/null_qubit/NullQubit.hpp +++ b/runtime/lib/backend/null_qubit/NullQubit.hpp @@ -251,8 +251,12 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { */ auto Observable(ObsId obs_id, const std::vector> &matrix, const std::vector &wires) -> ObsIdType - { - return instruction_str_builder.create_obs_str(obs_id, matrix, wires); + { + if (print_instructions) { + return instruction_str_builder.create_obs_str(obs_id, matrix, wires); + } + + return 0; } /** @@ -261,8 +265,12 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * @return `ObsIdType` Index of the constructed observable */ auto TensorObservable(const std::vector &obs_keys) -> ObsIdType - { - return instruction_str_builder.create_tensor_obs_str(obs_keys); + { + if (print_instructions) { + return instruction_str_builder.create_tensor_obs_str(obs_keys); + } + + return 0; } /** @@ -273,7 +281,11 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { auto HamiltonianObservable(const std::vector &matrix, const std::vector &obs_keys) -> ObsIdType { - return instruction_str_builder.create_hamiltonian_obs_str(matrix, obs_keys); + if (print_instructions) { + return instruction_str_builder.create_hamiltonian_obs_str(matrix, obs_keys); + } + + return 0; } /** From cb35bfed8d013adfe0d89ee1eac04f293a669e33 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:34:33 +0100 Subject: [PATCH 19/28] fixes comment typos Co-authored-by: David Ittah --- runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 3c36a686a9..75692fff5b 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -23,8 +23,8 @@ static const unordered_map obs_id_to_str = { * InstructionStrBuilder * * @brief This class is used by the null device (NullQubit.hpp) whenever the flag to print - * instructions (print_instructions) is set to true. It is in charge of building string repretations - * the operations invoked in the aformentioned device interface. + * instructions (print_instructions) is set to true. It is in charge of building string representations + * of the operations invoked in the aforementioned device interface. */ class InstructionStrBuilder { From 18653b574f2dbdfc319f35d7b8a439a552d0a3d9 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:43:58 +0100 Subject: [PATCH 20/28] refact: changes names of funcs --- .../backend/null_qubit/InstructionStrBuilder.hpp | 11 +++++------ runtime/lib/backend/null_qubit/NullQubit.hpp | 8 ++++---- runtime/tests/Test_InstructionStrBuilder.cpp | 14 +++++++------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 5598969230..0143cdd113 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -111,7 +111,7 @@ class InstructionStrBuilder { /** * @brief This method is used to build a string representation of AllocateQubit(), - * AllocateQubits(), ReleaseQubit(), ReleaseAllQubits(), Measure() + * AllocateQubits(), ReleaseQubit(), ReleaseAllQubits(), State(), Measure() */ template string get_simple_op_str(const string &name, const T ¶m) { @@ -123,7 +123,7 @@ class InstructionStrBuilder { /** * @brief This method is used to build a string representation of Expval() and Var() */ - string get_simple_op_str(const string &name, const ObsIdType &o) + string get_op_with_obs_str(const string &name, const ObsIdType &o) { ostringstream oss; oss << name << "(" << get_obs_str(o) << ")"; @@ -131,11 +131,10 @@ class InstructionStrBuilder { } /** - * @brief This method is used to build a string representation of State(), Probs(), + * @brief This method is used to build a string representation of Probs(), * PartialProbs() */ - template - string get_simple_op_str(const string &name, DataView &dataview, + string get_op_with_view_str(const string &name, DataView &dataview, const std::vector &wires = {}) { ostringstream oss; @@ -295,7 +294,7 @@ class InstructionStrBuilder { bool is_first = true; for (auto i = 0; i < coeffs.size(); i++) { if (!is_first) { - + // handle the addition of the terms if (coeffs[i] > 0) { oss << " + " << coeffs[i]; diff --git a/runtime/lib/backend/null_qubit/NullQubit.hpp b/runtime/lib/backend/null_qubit/NullQubit.hpp index 1e0c536398..4a719d177b 100644 --- a/runtime/lib/backend/null_qubit/NullQubit.hpp +++ b/runtime/lib/backend/null_qubit/NullQubit.hpp @@ -296,7 +296,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { auto Expval(ObsIdType o) -> double { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("Expval", o) << std::endl; + std::cout << instruction_str_builder.get_op_with_obs_str("Expval", o) << std::endl; } return 0.0; } @@ -309,7 +309,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { auto Var(ObsIdType o) -> double { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("Var", o) << std::endl; + std::cout << instruction_str_builder.get_op_with_obs_str("Var", o) << std::endl; } return 0.0; } @@ -330,7 +330,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void Probs(DataView &probs) { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("Probs", probs) << std::endl; + std::cout << instruction_str_builder.get_op_with_view_str("Probs", probs) << std::endl; } } @@ -340,7 +340,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void PartialProbs(DataView &probs, const std::vector &wires) { if (print_instructions) { - std::cout << instruction_str_builder.get_simple_op_str("PartialProbs", probs, wires) + std::cout << instruction_str_builder.get_op_with_view_str("PartialProbs", probs, wires) << std::endl; } } diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index d632ecad42..9d8c6e1b1c 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -19,20 +19,20 @@ TEST_CASE("string building is correct for instructions that require a parameter InstructionStrBuilder str_builder; auto id = str_builder.create_obs_str(ObsId::PauliX, {}, {0}); - CHECK(str_builder.get_simple_op_str("Expval", id) == "Expval(PauliX(0))"); - CHECK(str_builder.get_simple_op_str("Var", id) == "Var(PauliX(0))"); + CHECK(str_builder.get_op_with_obs_str("Expval", id) == "Expval(PauliX(0))"); + CHECK(str_builder.get_op_with_obs_str("Var", id) == "Var(PauliX(0))"); } TEST_CASE("string building is correct for instructions that require a parameter of type DataView", "[InstructionStrBuilder]") { InstructionStrBuilder str_builder; - std::vector> state(2); - DataView, 1> view(state); + std::vector state(2); + DataView view(state); - CHECK(str_builder.get_simple_op_str("Probs", view) == "Probs([0, 0])"); - CHECK(str_builder.get_simple_op_str("PartialProbs", view, {0}) == - "PartialProbs([0, 0], wires=[0])"); + CHECK(str_builder.get_op_with_view_str("Probs", view) == "Probs([0.000000, 0.000000])"); + CHECK(str_builder.get_op_with_view_str("PartialProbs", view, {0}) == + "PartialProbs([0.000000, 0.000000], wires=[0])"); } TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuilder]") From dccb9625abf8c28efbc822dba2240ed4e45130b5 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 11:46:36 +0100 Subject: [PATCH 21/28] make format --- frontend/catalyst/device/qjit_device.py | 2 +- frontend/catalyst/jit.py | 4 +-- frontend/catalyst/qfunc.py | 2 +- .../null_qubit/InstructionStrBuilder.hpp | 33 +++++++++++-------- runtime/lib/backend/null_qubit/NullQubit.hpp | 6 ++-- runtime/tests/Test_InstructionStrBuilder.cpp | 8 +++-- 6 files changed, 31 insertions(+), 24 deletions(-) diff --git a/frontend/catalyst/device/qjit_device.py b/frontend/catalyst/device/qjit_device.py index 225d56ce1f..3feefd3ce7 100644 --- a/frontend/catalyst/device/qjit_device.py +++ b/frontend/catalyst/device/qjit_device.py @@ -307,7 +307,7 @@ def __init__(self, original_device, print_instructions=False): self.backend_name = backend.c_interface_name self.backend_lib = backend.lpath self.backend_kwargs = backend.kwargs - + # include 'print_instructions' as a keyword argument for the device constructor. self.backend_kwargs["print_instructions"] = print_instructions diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 46c1c79c88..d3a2b773a2 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -156,7 +156,7 @@ def qjit( dictionaries of valid keyword arguments and values for the specific pass. The order of keys in this dictionary will determine the pass pipeline. If not specified, the default pass pipeline will be applied. - print_instructions (Optional[bool]): + print_instructions (Optional[bool]): If set to True, instructions are printed when executing in the null device. Returns: @@ -458,7 +458,7 @@ class QJIT(CatalystCallable): Args: fn (Callable): the quantum or classical function to compile compile_options (CompileOptions): compilation options to use - print_instructions (Optional[bool]): If True, prints instructions + print_instructions (Optional[bool]): If True, prints instructions when executing in a null device. :ivar original_function: This attribute stores `fn`, the quantum or classical function diff --git a/frontend/catalyst/qfunc.py b/frontend/catalyst/qfunc.py index c2a4d9653d..296318da07 100644 --- a/frontend/catalyst/qfunc.py +++ b/frontend/catalyst/qfunc.py @@ -122,7 +122,7 @@ def __call__(self, *args, **kwargs): mcm_config.postselect_mode = mcm_config.postselect_mode or "hw-like" return Function(dynamic_one_shot(self, mcm_config=mcm_config))(*args, **kwargs) - # retrieve the flag to print instructions, used for executing + # retrieve the flag to print instructions, used for executing # pre-compiled programs in a null device. print_instructions = kwargs.pop("print_instructions", False) qjit_device = QJITDevice(self.device, print_instructions) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 2a0833eae9..f38263d4aa 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -23,8 +23,8 @@ static const unordered_map obs_id_to_str = { * InstructionStrBuilder * * @brief This class is used by the null device (NullQubit.hpp) whenever the flag to print - * instructions (print_instructions) is set to true. It is in charge of building string representations - * of the operations invoked in the aforementioned device interface. + * instructions (print_instructions) is set to true. It is in charge of building string + * representations of the operations invoked in the aforementioned device interface. */ class InstructionStrBuilder { @@ -50,11 +50,11 @@ class InstructionStrBuilder { // to keep printing output as short as possible, we only print the imaginary part // whenever is different from zero; if (c.imag() > 0) { - oss << " + " << c.imag() << "i"; - } else { - oss << " - " << -1*c.imag() << "i"; + oss << " + " << c.imag() << "i"; + } + else { + oss << " - " << -1 * c.imag() << "i"; } - } return oss.str(); } @@ -135,7 +135,7 @@ class InstructionStrBuilder { * PartialProbs() */ string get_op_with_view_str(const string &name, DataView &dataview, - const std::vector &wires = {}) + const std::vector &wires = {}) { ostringstream oss; oss << name << "(" << get_dataview_str(dataview); @@ -152,7 +152,8 @@ class InstructionStrBuilder { string get_named_op_str(const std::string &name, const std::vector ¶ms, const std::vector &wires, bool inverse = false, const std::vector &controlled_wires = {}, - const std::vector &controlled_values = {}, const bool &explicit_wires=true) + const std::vector &controlled_values = {}, + const bool &explicit_wires = true) { ostringstream oss; vector values_to_print; // Store the string representation of the parameters passed @@ -165,10 +166,10 @@ class InstructionStrBuilder { if (wires.size() > 0) { if (explicit_wires) { values_to_print.push_back("wires=" + vector_to_string(wires, true)); - } else { + } + else { values_to_print.push_back(vector_to_string(wires, false)); } - } // if inverse is false, we will not print its value @@ -248,7 +249,8 @@ class InstructionStrBuilder { else { auto it = obs_id_to_str.find(obs_id); if (it != obs_id_to_str.end()) { - obs_id_type_to_str.emplace(new_id, get_named_op_str(it->second, {}, wires, false, {}, {}, false)); + obs_id_type_to_str.emplace( + new_id, get_named_op_str(it->second, {}, wires, false, {}, {}, false)); } else { RT_FAIL( @@ -295,17 +297,20 @@ class InstructionStrBuilder { for (auto i = 0; i < coeffs.size(); i++) { if (!is_first) { - // handle the addition of the terms + // handle the addition of the terms if (coeffs[i] > 0) { oss << " + " << coeffs[i]; } else if (coeffs[i] < 0) { - oss << " - " << -1*coeffs[i]; // a negative sign is manually added so we multiply the coefficient by -1 + oss << " - " << -1 * coeffs[i]; // a negative sign is manually added so we + // multiply the coefficient by -1 } } if (coeffs[i] != 0) { - if (is_first) oss << coeffs[i]; // if is the first element then this coefficient is not yet added to the string + if (is_first) + oss << coeffs[i]; // if is the first element then this coefficient is not yet + // added to the string oss << "*" << obs_id_type_to_str[obs_keys[i]]; is_first = false; } diff --git a/runtime/lib/backend/null_qubit/NullQubit.hpp b/runtime/lib/backend/null_qubit/NullQubit.hpp index 4a719d177b..4891c5394c 100644 --- a/runtime/lib/backend/null_qubit/NullQubit.hpp +++ b/runtime/lib/backend/null_qubit/NullQubit.hpp @@ -251,7 +251,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { */ auto Observable(ObsId obs_id, const std::vector> &matrix, const std::vector &wires) -> ObsIdType - { + { if (print_instructions) { return instruction_str_builder.create_obs_str(obs_id, matrix, wires); } @@ -265,7 +265,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { * @return `ObsIdType` Index of the constructed observable */ auto TensorObservable(const std::vector &obs_keys) -> ObsIdType - { + { if (print_instructions) { return instruction_str_builder.create_tensor_obs_str(obs_keys); } @@ -284,7 +284,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { if (print_instructions) { return instruction_str_builder.create_hamiltonian_obs_str(matrix, obs_keys); } - + return 0; } diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index a2f1fb04af..74a08db016 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -55,11 +55,13 @@ TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuild TEST_CASE("string building is correct for MatrixOperation", "[InstructionStrBuilder]") { InstructionStrBuilder str_builder; - std::vector> v = {std::complex(0.707, -0.707), std::complex(0.0), - std::complex(0.0), std::complex(1.0)}; + std::vector> v = {std::complex(0.707, -0.707), + std::complex(0.0), std::complex(0.0), + std::complex(1.0)}; CHECK(str_builder.get_matrix_op_str(v, {}) == "MatrixOperation([0.707 - 0.707i, 0, 0, 1])"); - CHECK(str_builder.get_matrix_op_str(v, {0}) == "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[0])"); + CHECK(str_builder.get_matrix_op_str(v, {0}) == + "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[0])"); CHECK(str_builder.get_matrix_op_str(v, {0}, true) == "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[0], inverse=true)"); CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}) == From b3df66742d4bd2d414324f095cbfce11b743ea32 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 12:07:39 +0100 Subject: [PATCH 22/28] update changelog --- doc/releases/changelog-dev.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 1529550f12..be1c3d9ca1 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -1,6 +1,8 @@ # Release 0.10.0-dev (development release)

New features since last release

+* The PennyLane plugin now has the option to print out instructions in the null device + [(#1316)](https://github.com/PennyLaneAI/catalyst/pull/1346)

Improvements 🛠

@@ -89,4 +91,5 @@ Mehrdad Malekmohammadi, William Maxwell Romain Moyard, Raul Torres, -Paul Haochen Wang. +Paul Haochen Wang, +Stefanie Muroya Lei. From 4c82c8de0b31abd0ccead544c1e9baa2857aebc5 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 12:10:24 +0100 Subject: [PATCH 23/28] Update changelog-dev.md --- doc/releases/changelog-dev.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index be1c3d9ca1..130f2ccb04 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -1,6 +1,7 @@ # Release 0.10.0-dev (development release)

New features since last release

+ * The PennyLane plugin now has the option to print out instructions in the null device [(#1316)](https://github.com/PennyLaneAI/catalyst/pull/1346) From eb6ad9db587067a4a6e7a317b5a44a0ded317f7f Mon Sep 17 00:00:00 2001 From: smml1996 Date: Sun, 1 Dec 2024 12:20:27 +0100 Subject: [PATCH 24/28] refact: minor change --- runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index f38263d4aa..dcb6a43abf 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -51,8 +51,7 @@ class InstructionStrBuilder { // whenever is different from zero; if (c.imag() > 0) { oss << " + " << c.imag() << "i"; - } - else { + } else { oss << " - " << -1 * c.imag() << "i"; } } From 8b5af553326e6bd48973ee7aa48ebed74e7f3486 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Mon, 2 Dec 2024 17:34:50 +0100 Subject: [PATCH 25/28] removes string representation of DataViews --- .../null_qubit/InstructionStrBuilder.hpp | 89 +++---------------- runtime/lib/backend/null_qubit/NullQubit.hpp | 16 ++-- runtime/tests/Test_InstructionStrBuilder.cpp | 27 ++---- 3 files changed, 23 insertions(+), 109 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index dcb6a43abf..baaa079e94 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -1,4 +1,3 @@ - #pragma once #include @@ -51,7 +50,8 @@ class InstructionStrBuilder { // whenever is different from zero; if (c.imag() > 0) { oss << " + " << c.imag() << "i"; - } else { + } + else { oss << " - " << -1 * c.imag() << "i"; } } @@ -83,34 +83,13 @@ class InstructionStrBuilder { return oss.str(); } - /** - * @brief Builds the string representation of DataView - */ - template string get_dataview_str(DataView &dataview) - { - ostringstream oss; - bool is_first = true; // boolean to help determine where to put commas - - oss << "["; - for (auto it = dataview.begin(); it != dataview.end(); it++) { - if (!is_first) { - oss << ", "; // if is not the first element then we add a comma to separate the - // elements - } - oss << element_to_str(*it); - is_first = false; - } - oss << "]"; - return oss.str(); - } - public: InstructionStrBuilder() = default; ~InstructionStrBuilder() = default; /** * @brief This method is used to build a string representation of AllocateQubit(), - * AllocateQubits(), ReleaseQubit(), ReleaseAllQubits(), State(), Measure() + * AllocateQubits(), ReleaseQubit(), ReleaseAllQubits(), State(), Probs(), Measure() */ template string get_simple_op_str(const string &name, const T ¶m) { @@ -130,24 +109,9 @@ class InstructionStrBuilder { } /** - * @brief This method is used to build a string representation of Probs(), + * @brief This method is used to get the string representation of NamedOperation(), * PartialProbs() */ - string get_op_with_view_str(const string &name, DataView &dataview, - const std::vector &wires = {}) - { - ostringstream oss; - oss << name << "(" << get_dataview_str(dataview); - if (wires.size() > 0) { - oss << ", wires=" << vector_to_string(wires); - } - oss << ")"; - return oss.str(); - } - - /** - * @brief This method is used to get the string representation of NamedOperation() - */ string get_named_op_str(const std::string &name, const std::vector ¶ms, const std::vector &wires, bool inverse = false, const std::vector &controlled_wires = {}, @@ -326,55 +290,22 @@ class InstructionStrBuilder { string get_obs_str(const ObsIdType &o) { return obs_id_type_to_str.at(o); } /** - * @brief This method is used to get the string representation of Sample() and PartialSample() + * @brief This method is used to get the string representation of Sample(), PartialSample(), + * Counts() and PartialCounts(). */ - string get_samples_str(const string &name, DataView &samples, const size_t &shots, - const vector &wires = {}) + string get_distribution_op_str(const string &name, const size_t &shots, + const vector &wires = {}) { - ostringstream oss; - bool is_first = true; - oss << name << "(" << get_dataview_str(samples); - - if (wires.size() > 0) { - oss << ", wires=" << vector_to_string(wires); - } - - oss << ", shots=" << shots << ")"; - - return oss.str(); - } - - /** - * @brief This method is used to get the string representation of Counts() and PartialCounts(). - */ - string get_counts_str(const string &name, DataView &vals, - DataView &counts, const size_t &shots, - const vector &wires = {}) - { - RT_FAIL_IF(vals.size() != counts.size(), "number of eigenvalues does not matches counts"); - ostringstream oss; bool is_first = true; oss << name << "("; - oss << "["; - auto it1 = vals.begin(); - auto it2 = counts.begin(); - for (; it1 != vals.end(); it1++, it2++) { - if (!is_first) { - oss << ", "; - } - // here we build a substring that represents a pair (eigenval, count) - oss << "(" << element_to_str(*it1) << ", " << element_to_str(*it2) << ")"; - is_first = false; - } - oss << "]"; // + + oss << "shots=" << shots; if (wires.size() > 0) { oss << ", wires=" << vector_to_string(wires); } - oss << ", shots=" << shots; - oss << ")"; return oss.str(); diff --git a/runtime/lib/backend/null_qubit/NullQubit.hpp b/runtime/lib/backend/null_qubit/NullQubit.hpp index 4891c5394c..8db79e7be5 100644 --- a/runtime/lib/backend/null_qubit/NullQubit.hpp +++ b/runtime/lib/backend/null_qubit/NullQubit.hpp @@ -330,7 +330,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void Probs(DataView &probs) { if (print_instructions) { - std::cout << instruction_str_builder.get_op_with_view_str("Probs", probs) << std::endl; + std::cout << instruction_str_builder.get_simple_op_str("Probs", "") << std::endl; } } @@ -340,7 +340,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void PartialProbs(DataView &probs, const std::vector &wires) { if (print_instructions) { - std::cout << instruction_str_builder.get_op_with_view_str("PartialProbs", probs, wires) + std::cout << instruction_str_builder.get_named_op_str("PartialProbs", {}, wires) << std::endl; } } @@ -352,7 +352,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void Sample(DataView &samples, size_t shots) { if (print_instructions) { - std::cout << instruction_str_builder.get_samples_str("Sample", samples, shots) + std::cout << instruction_str_builder.get_distribution_op_str("Sample", shots) << std::endl; } } @@ -369,8 +369,8 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { size_t shots) { if (print_instructions) { - std::cout << instruction_str_builder.get_samples_str("PartialSample", samples, shots, - wires) + std::cout << instruction_str_builder.get_distribution_op_str("PartialSample", shots, + wires) << std::endl; } } @@ -382,7 +382,7 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { void Counts(DataView &eigen_vals, DataView &counts, size_t shots) { if (print_instructions) { - std::cout << instruction_str_builder.get_counts_str("Counts", eigen_vals, counts, shots) + std::cout << instruction_str_builder.get_distribution_op_str("Counts", shots) << std::endl; } } @@ -395,8 +395,8 @@ struct NullQubit final : public Catalyst::Runtime::QuantumDevice { const std::vector &wires, size_t shots) { if (print_instructions) { - std::cout << instruction_str_builder.get_counts_str("PartialCounts", eigen_vals, counts, - shots, wires) + std::cout << instruction_str_builder.get_distribution_op_str("PartialCounts", shots, + wires) << std::endl; } } diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index 74a08db016..b18c05a54d 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -10,6 +10,7 @@ TEST_CASE("string building is correct for instructions that require up to one pa CHECK(str_builder.get_simple_op_str("ReleaseQubit", "") == "ReleaseQubit()"); CHECK(str_builder.get_simple_op_str("ReleaseAllQubits", "") == "ReleaseAllQubits()"); CHECK(str_builder.get_simple_op_str("State", "") == "State()"); + CHECK(str_builder.get_simple_op_str("Probs", "") == "Probs()"); CHECK(str_builder.get_simple_op_str("Measure", 0) == "Measure(0)"); } @@ -23,18 +24,6 @@ TEST_CASE("string building is correct for instructions that require a parameter CHECK(str_builder.get_op_with_obs_str("Var", id) == "Var(PauliX(0))"); } -TEST_CASE("string building is correct for instructions that require a parameter of type DataView", - "[InstructionStrBuilder]") -{ - InstructionStrBuilder str_builder; - std::vector state(2); - DataView view(state); - - CHECK(str_builder.get_op_with_view_str("Probs", view) == "Probs([0.000000, 0.000000])"); - CHECK(str_builder.get_op_with_view_str("PartialProbs", view, {0}) == - "PartialProbs([0.000000, 0.000000], wires=[0])"); -} - TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuilder]") { InstructionStrBuilder str_builder; @@ -50,6 +39,7 @@ TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuild "NamedOperation(wires=[1], control=[0])"); CHECK(str_builder.get_named_op_str("NamedOperation", {}, {1}, false, {0}, {true}) == "NamedOperation(wires=[1], control=[0], control_value=[1])"); + CHECK(str_builder.get_named_op_str("PartialProbs", {}, {0}) == "PartialProbs(wires=[0])"); } TEST_CASE("string building is correct for MatrixOperation", "[InstructionStrBuilder]") @@ -100,14 +90,7 @@ TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") TEST_CASE("string building for Counts() and PartialCounts()", "[InstructionStrBuilder]") { InstructionStrBuilder str_builder; - std::vector state(2); - DataView state_view(state); - - std::vector counts(2); - DataView counts_view(counts); - - CHECK(str_builder.get_counts_str("Counts", state_view, counts_view, 100) == - "Counts([(0.000000, 0), (0.000000, 0)], shots=100)"); - CHECK(str_builder.get_counts_str("PartialCounts", state_view, counts_view, 100, {0}) == - "PartialCounts([(0.000000, 0), (0.000000, 0)], wires=[0], shots=100)"); + CHECK(str_builder.get_distribution_op_str("Counts", 100) == "Counts(shots=100)"); + CHECK(str_builder.get_distribution_op_str("PartialCounts", 100, {0}) == + "PartialCounts(shots=100, wires=[0])"); } From 975cb7dc98d93123f063cc3c4301c2144480e220 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Mon, 2 Dec 2024 17:52:01 +0100 Subject: [PATCH 26/28] removes blank line --- runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index baaa079e94..09585fffef 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -25,7 +25,6 @@ static const unordered_map obs_id_to_str = { * instructions (print_instructions) is set to true. It is in charge of building string * representations of the operations invoked in the aforementioned device interface. */ - class InstructionStrBuilder { private: unordered_map From 5ea6b8e7e4b6cfa19cf0e9c04ac49aedd8dd75b6 Mon Sep 17 00:00:00 2001 From: smml1996 Date: Mon, 2 Dec 2024 18:18:16 +0100 Subject: [PATCH 27/28] handle zero coefficient for real part in imaginary number string representation --- .../null_qubit/InstructionStrBuilder.hpp | 28 +++++++++++++++---- runtime/tests/Test_InstructionStrBuilder.cpp | 24 ++++++++++------ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp index 09585fffef..e58d166442 100644 --- a/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp +++ b/runtime/lib/backend/null_qubit/InstructionStrBuilder.hpp @@ -42,18 +42,34 @@ class InstructionStrBuilder { template string element_to_str(const complex &c) { ostringstream oss; - oss << c.real(); + if (c.real() == 0 && c.imag() == 0) { + oss << 0; + return oss.str(); + } + + bool is_first = true; // keep track in which term we are so we can add '+' or '-' + // appropriately + + // to keep printing output as short as possible, we only print non-zero coefficients + if (c.real() != 0) { + oss << c.real(); + is_first = false; + } if (c.imag() != 0) { - // to keep printing output as short as possible, we only print the imaginary part - // whenever is different from zero; - if (c.imag() > 0) { - oss << " + " << c.imag() << "i"; + if (!is_first) { + if (c.imag() > 0) { + oss << " + " << c.imag() << "i"; + } + else { + oss << " - " << -1 * c.imag() << "i"; + } } else { - oss << " - " << -1 * c.imag() << "i"; + oss << c.imag() << "i"; } } + return oss.str(); } diff --git a/runtime/tests/Test_InstructionStrBuilder.cpp b/runtime/tests/Test_InstructionStrBuilder.cpp index b18c05a54d..1a0c94eec1 100644 --- a/runtime/tests/Test_InstructionStrBuilder.cpp +++ b/runtime/tests/Test_InstructionStrBuilder.cpp @@ -45,19 +45,27 @@ TEST_CASE("string building is correct for NamedOperation", "[InstructionStrBuild TEST_CASE("string building is correct for MatrixOperation", "[InstructionStrBuilder]") { InstructionStrBuilder str_builder; - std::vector> v = {std::complex(0.707, -0.707), - std::complex(0.0), std::complex(0.0), - std::complex(1.0)}; + std::vector> v = { + std::complex(0.707, -0.707), std::complex(0.0, 0.707), + std::complex(0, -0.707), std::complex(1.0), + std::complex(-1.0), std::complex(0), + std::complex(0.707, 0.707), std::complex(-0.707, 0.707)}; - CHECK(str_builder.get_matrix_op_str(v, {}) == "MatrixOperation([0.707 - 0.707i, 0, 0, 1])"); + CHECK(str_builder.get_matrix_op_str(v, {}) == + "MatrixOperation([0.707 - 0.707i, 0.707i, -0.707i, 1, -1, 0, 0.707 + 0.707i, -0.707 + " + "0.707i])"); CHECK(str_builder.get_matrix_op_str(v, {0}) == - "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[0])"); + "MatrixOperation([0.707 - 0.707i, 0.707i, -0.707i, 1, -1, 0, 0.707 + 0.707i, -0.707 + " + "0.707i], wires=[0])"); CHECK(str_builder.get_matrix_op_str(v, {0}, true) == - "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[0], inverse=true)"); + "MatrixOperation([0.707 - 0.707i, 0.707i, -0.707i, 1, -1, 0, 0.707 + 0.707i, -0.707 + " + "0.707i], wires=[0], inverse=true)"); CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}) == - "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[1], control=[0])"); + "MatrixOperation([0.707 - 0.707i, 0.707i, -0.707i, 1, -1, 0, 0.707 + 0.707i, -0.707 + " + "0.707i], wires=[1], control=[0])"); CHECK(str_builder.get_matrix_op_str(v, {1}, false, {0}, {true}) == - "MatrixOperation([0.707 - 0.707i, 0, 0, 1], wires=[1], control=[0], control_value=[1])"); + "MatrixOperation([0.707 - 0.707i, 0.707i, -0.707i, 1, -1, 0, 0.707 + 0.707i, -0.707 + " + "0.707i], wires=[1], control=[0], control_value=[1])"); } TEST_CASE("registers correctly a new observable", "[InstructionStrBuilder]") From a5f7c3055e0924190af9c16d70c083833340defe Mon Sep 17 00:00:00 2001 From: smml1996 Date: Tue, 3 Dec 2024 00:05:44 +0100 Subject: [PATCH 28/28] fix: only pass print_instructions when using null.qubit device --- frontend/catalyst/device/qjit_device.py | 5 +++-- frontend/catalyst/jit.py | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/catalyst/device/qjit_device.py b/frontend/catalyst/device/qjit_device.py index 7af89b8db2..6b1b914ac7 100644 --- a/frontend/catalyst/device/qjit_device.py +++ b/frontend/catalyst/device/qjit_device.py @@ -332,8 +332,9 @@ def __init__(self, original_device, print_instructions=False): self.backend_lib = backend.lpath self.backend_kwargs = backend.kwargs - # include 'print_instructions' as a keyword argument for the device constructor. - self.backend_kwargs["print_instructions"] = print_instructions + if original_device.name == "null.qubit": + # include 'print_instructions' as a keyword argument for the device constructor. + self.backend_kwargs["print_instructions"] = print_instructions self.capabilities = get_qjit_device_capabilities(device_capabilities) diff --git a/frontend/catalyst/jit.py b/frontend/catalyst/jit.py index 5edd933333..9f3c4e58d1 100644 --- a/frontend/catalyst/jit.py +++ b/frontend/catalyst/jit.py @@ -644,10 +644,13 @@ def closure(qnode, *args, **kwargs): params = {} params["static_argnums"] = kwargs.pop("static_argnums", static_argnums) params["_out_tree_expected"] = [] + + if qnode.device.name == "null.qubit": + kwargs["print_instructions"] = self.print_instructions + return QFunc.__call__( qnode, pass_pipeline=self.compile_options.circuit_transform_pipeline, - print_instructions=self.print_instructions, *args, **dict(params, **kwargs), )