Quartz is a quantum circuit optimizer that automatically generates and verifies circuit transformations for an arbitrary quantum gate set. To optimize an input quantum circuit, Quartz uses these auto-generated circuit transformations to construct a search space of functionally equivalent quantum circuits. Quartz uses a cost-based search algorithm to explore the space and discovers highly optimized quantum circuits.
See instructions to install Quartz from source code.
Quartz targets the logical optimization stage in quantum circuit compilation and can be used to optimize quantum circuits for arbitrary gate sets (e.g., IBM or Regetti quantum processors). Quartz works in two steps. First, for a given gate set, the Quartz circuit generator and circuit equivalence verifier can automatically generate and verify possible circuit transformations, represented as an equivalent circuit class (ECC) set. Second, Quartz's circuit optimizer takes a quantum circuit and an ECC set as inputs and use cost-based backtracking search to discover a super-optimized quantum circuit.
To generate and verify pre-defined ECC sets, you can simply run ./gen_ecc_set.sh
.
To generate an (n,q)
-complete ECC set with m
input parameters for some gate set,
you can change the main function in src/test/gen_ecc_set.cpp
to the following:
gen_ecc_set({Gate set}, "{Name of the gate set}_{n}_{q}_", true, true, q, m, n);
return 0;
where {Gate set}
can be {GateType::rz, GateType::h, GateType::cx, GateType::x, GateType::add}
for the Nam gate set,
{GateType::u1, GateType::u2, GateType::u3, GateType::cx, GateType::add}
for the IBM gate set,
{GateType::rx, GateType::rz, GateType::cz, GateType::add}
for the Rigetti gate set,
or any gate set you want. GateType::add
is to enable using a sum of two input parameters as an input to a parameterized quantum gate.
See all supported gate types in gates.inc.h and their implementations in gate/.
And then you can run ./gen_ecc_set.sh
to generate the ECC set.
We show the steps to super-optimize a quantum circuit in Quartz.
To optimize a circuit, you can write your circuit in the qasm
language and write it to a qasm
file.
Currently, we only support a subset of qasm
's grammar.
Specifically, the qasm
files we support should consist of a header and lines of qasm
instructions.
The header should be in the format below:
OPENQASM 2.0;
include "qelib1.inc";
qreg q[24];
The instructions should be in the format below:
cx q[3], q[2];
cx q[8], q[7];
cx q[14], q[13];
cx q[21], q[20];
We do not support parameterized gates currently.
To input a circuit in qasm
file, you should first create a Context
object, providing the gate set you use in your input file as argument as below:
Context src_ctx({GateType::h, GateType::ccz, GateType::x, GateType::cx,
GateType::input_qubit, GateType::input_param});
After that, you need a QASMParser
object to parse the input qasm
file. You can construct it as below:
QASMParser qasm_parser(&src_ctx);
Now you can use the QASMParser
object to load the circuit from the qasm
file to a DAG
object, as below:
DAG *dag = nullptr;
if (!qasm_parser.load_qasm(input_fn, dag)) {
std::cout << "Parser failed" << std::endl;
}
After you have the circuit loaded into the DAG
object, you can construct a Graph
object from it. The Graph
object is the final circuit representation used in our optimizer. You can construct it as below:
Graph graph(&src_ctx, dag);
If the input gate set is different from your target gate set, you should consider using the context_shift
APIs to shift the context constructed with the gate sets to a context constructed with the target gate set.
To shift the context, you should create three Contxt
objects, one for input, one for target, and one for their union as below:
Context src_ctx({GateType::h, GateType::ccz, GateType::x, GateType::cx,
GateType::input_qubit, GateType::input_param});
Context dst_ctx({GateType::h, GateType::x, GateType::rz, GateType::add,
GateType::cx, GateType::input_qubit, GateType::input_param});
auto union_ctx = union_contexts(&src_ctx, &dst_ctx);
In order to shift contexts, you should provide the rules to express a gate in the input gate set to the target gate set. To do this, you should construct a RuleParser
object. As follows:
RuleParser rules(
{"cx q0 q1 = rx q1 pi; rz q1 0.5pi; rx q1 0.5pi; rz q1 -0.5pi; cz q0 "
"q1; rx q1 pi; rz q1 0.5pi; rx q1 0.5pi; rz q1 -0.5pi;",
"h q0 = rx q0 pi; rz q0 0.5pi; rx q0 0.5pi; rz q0 -0.5pi;",
"x q0 = rx q0 pi;"});
As shown in the example above, the grammar for the rules are simple. Also, if a gate in the input gate set already appears in the target set, you don't have to provide a rule for it.
You can use the API:
std::shared_ptr<Graph> optimize(Context *ctx,
const std::string &equiv_file_name,
const std::string &circuit_name,
bool print_message,
std::function<float(Graph *)> cost_function = nullptr,
double cost_upper_bound = -1 /*default = current cost * 1.05*/,
int timeout = 3600 /*1 hour*/);
Explanation for some of the parameters:
equiv_file_name
: The file name of the ECC set.circuit_name
: The name of the circuit, which will be printed with the intermediate result.print_message
: Print debug message to the console.cost_function
: The cost function used in the search.cost_upper_bound
: Maximum circuit cost to be searched during optimization.timeout
: Timeout for optimization in seconds.
Usage example:
auto graph_optimized = graph->optimize(&context,
equiv_file_name,
circuit_name,
/*print_message=*/true,
[] (Graph *graph) { return graph->total_cost(); },
/*cost_upper_bound=*/-1,
/*timeout=*/10);
You can also use the deprecated API for now:
Graph::optimize_legacy(float alpha, int budget, bool print_subst, Context *ctx,
const std::string &equiv_file_name, bool use_simulated_annealing,
bool enable_early_stop, bool use_rotation_merging_in_searching,
GateType target_rotation, std::string circuit_name = "",
int timeout = 86400 /*1 day*/);
Explanation for some of the parameters:
print_subst
: Deprecated will be removed in future version.equiv_file_name
: The file name of the ECC set.use_simulated_annealing
: Use simulated annealing in searching.use_rotation_merging_in_searching
: Enable rotation merging in each iteration of the back-track searching.target_rotation
: The target rotation used if you enable rotation merging in search.circuit_name
: The name of the circuit, which will be printed with the intermediate result.timeout
: Timeout for optimization in seconds.
See code structure for more information about the organization of the Quartz code base.
Please let us know if you encounter any bugs or have any suggestions by submitting an issue.
We welcome all contributions to Quartz from bug fixes to new features and extensions.
Please follow developer guidance.
Please subscribe to the Quartz users mailing list (TODO)
- Mingkuan Xu, Zikun Li, Oded Padon, Sina Lin, Jessica Pointing, Auguste Hirth, Henry Ma, Jens Palsberg, Alex Aiken, Umut A. Acar, and Zhihao Jia. Quartz: Superoptimization of Quantum Circuits. In Proceedings of the Conference on Programming Language Design and Implementation (PLDI), June 2022.
Quartz uses Apache License 2.0.