38 #include <unordered_map> 74 std::vector<int> I(n, 0);
75 for (
int i = 0; i <
n; ++i)
77 std::set<std::vector<int>>
visited;
86 result.
visited = std::move(visited);
88 result.
out_res = std::move(out_res);
96 const int m =
static_cast<int>(path.
size());
101 std::vector<std::vector<int>> succ(m);
102 std::vector<int> indeg(m, 0);
103 std::unordered_map<int, int> last_on;
104 last_on.reserve(m * 2);
106 for (
int k = 0;
k < m; ++
k) {
109 for (
int q : {a, b}) {
110 std::unordered_map<int, int>::iterator it = last_on.find(q);
111 if (it != last_on.end()) {
112 int prev = it->second;
113 succ[prev].push_back(
k);
117 last_on.emplace(q,
k);
124 std::pair<int, int> p;
128 bool operator()(
const Node& a,
const Node& b)
const {
131 return a.idx > b.idx;
134 std::priority_queue<Node, std::vector<Node>, Cmp> pq;
135 for (
int k = 0;
k < m; ++
k)
140 for (
int pos = 0; pos < m; ++pos) {
148 for (
int v : succ[u.idx]) {
157 for (
int idx = 0; idx < path.
size() - 3; idx++) {
158 if (path.
data[idx] == path.
data[idx + 1] && path.
data[idx] == path.
data[idx + 2] && path.
data[idx] == path.
data[idx + 3]) {
195 bool use_gl =
true) {
198 std::vector<std::vector<int>>& q = L.
q;
199 std::map<std::vector<int>,
GrayCodeCNOT> new_seq_pairs_of;
203 std::vector<int> A = q.back();
211 std::pair<int, int> m2 = {topology[p][1], topology[p][0]};
214 if (last_pairs.
size() >= 3 &&
215 std::all_of(last_pairs.
data + last_pairs.
size() - 3, last_pairs.
data + last_pairs.
size(),
216 [p](
const int&
x) {
return x == p; }))
219 seqp[seqp.
size() - 1] = p;
224 std::vector<std::pair<int, int>> allmv =
225 use_gl ? std::vector<std::pair<int, int>>{m1, m2} : std::vector<std::pair<int, int>>{m1};
227 for (std::pair<int, int> mv : allmv) {
231 if (mv.first != mv.second) {
232 B[mv.second] ^= B[mv.first];
235 if (visited.find(B) != visited.end()) {
239 B = std::vector<int>(last_pairs.
data, last_pairs.
data + last_pairs.
size());
246 seqp[seqp.
size() - 1] = p;
248 new_seq_pairs_of.emplace(B, std::move(seqp));
252 out_res.emplace_back(std::move(B), ref_pairs);
257 result.
visited = std::move(visited);
259 result.
out_res = std::move(out_res);
264 template <
class Callback>
268 const std::vector<int>& topo_filt,
270 std::vector<int>& places,
271 std::vector<int>& pairs,
277 const int nslots = curpath.
size() + 1;
279 if (depth == num_cnot) {
285 for (
int slot = 0; slot < nslots; ++slot) {
286 while (j < num_cnot && places[j] == slot) {
287 if (
k > 2 && out[
k-1] == pairs[j] && out[
k-2] == pairs[j] && out[
k-3] == pairs[j]) {
293 if (slot < curpath.
size()) {
294 if (
k > 2 && out[
k-1] == curpath[slot] && out[
k-2] == curpath[slot] && out[
k-3] == curpath[slot]) {
297 out[
k++] = curpath[slot];
300 early_stop |= callback(out);
303 uint32_t used_mask = 0u;
304 for (
int d = 0; d < depth; d++) {
307 for (
int place = min_place; place < nslots; ++place) {
308 if (depth != 0 && places[depth-1]+1 < place) {
311 places[depth] = place;
312 for (
int topo_idx : topo_filt) {
313 uint32_t edge_mask = (1u<<
topology[topo_idx][0]) | (1u<<
topology[topo_idx][1]);
314 if (depth != 0 && (used_mask & edge_mask) == 0)
continue;
315 pairs[depth] = topo_idx;
317 curpath,
topology, topo_filt, num_cnot,
318 places, pairs, depth + 1, place,
319 callback, early_stop);
320 if (early_stop)
return;
325 template <
class Callback>
329 const std::vector<int>& topo_filt,
333 std::vector<int> places(num_cnot);
334 std::vector<int> pairs(num_cnot);
335 bool early_stop =
false;
337 curpath,
topology, topo_filt, num_cnot,
339 std::forward<Callback>(callback), early_stop);
375 std::map<std::string, Config_Element>&
config,
383 std::map<std::string, Config_Element>&
config,
398 std::map<std::string, Config_Element>&
config,
412 for (
int qbit1 = 0; qbit1 <
qbit_num; qbit1++) {
413 for (
int qbit2 = qbit1 + 1; qbit2 <
qbit_num; qbit2++) {
422 for (
size_t idx = 0; idx <
topology.size(); idx++) {
424 std::string error(
"invalid topology: each element should be a pair of integers");
428 std::string error(
"invalid topology: qubit indices should be between 0 and qbit_num-1");
432 std::string error(
"invalid topology: target and control qubits should be different");
443 int n_ary_limit_max =
static_cast<int>(
topology.size());
447 for (
int element_idx = 0; element_idx < n_ary_limit_max; element_idx++) {
451 possible_control_qbits[element_idx] = edge[1];
474 std::map<std::string, Config_Element>&
config,
488 for (
int qbit1 = 0; qbit1 <
qbit_num; qbit1++) {
489 for (
int qbit2 = qbit1 + 1; qbit2 <
qbit_num; qbit2++) {
498 for (
size_t idx = 0; idx <
topology.size(); idx++) {
500 std::string error(
"invalid topology: each element should be a pair of integers");
504 std::string error(
"invalid topology: qubit indices should be between 0 and qbit_num-1");
508 std::string error(
"invalid topology: target and control qubits should be different");
519 int n_ary_limit_max =
static_cast<int>(
topology.size());
523 for (
int element_idx = 0; element_idx < n_ary_limit_max; element_idx++) {
527 possible_control_qbits[element_idx] = edge[1];
558 std::stringstream sstream;
559 sstream <<
"***************************************************************" << std::endl;
560 sstream <<
"Starting to disentangle " <<
qbit_num <<
"-qubit matrix" << std::endl;
561 sstream <<
"***************************************************************" << std::endl << std::endl << std::endl;
566 #if BLAS == 0 // undefined BLAS 569 #elif BLAS == 1 // MKL 571 MKL_Set_Num_Threads(1);
572 #elif BLAS == 2 // OpenBLAS 574 openblas_set_num_threads(1);
579 long long export_circuit_2_binary_loc;
580 if (
config.count(
"export_circuit_2_binary") > 0) {
581 config[
"export_circuit_2_binary"].get_property(export_circuit_2_binary_loc);
583 export_circuit_2_binary_loc = 0;
586 if (export_circuit_2_binary_loc > 0) {
587 std::string
filename(
"circuit_squander.binary");
593 std::string unitaryname(
"unitary_squander.binary");
603 delete (gate_structure_loc);
607 #if BLAS == 0 // undefined BLAS 609 #elif BLAS == 1 // MKL 611 #elif BLAS == 2 // OpenBLAS 623 double optimization_tolerance_loc;
624 long long level_max = 14;
625 if (
config.count(
"optimization_tolerance") > 0) {
626 config[
"optimization_tolerance"].get_property(optimization_tolerance_loc);
632 if (
config.count(
"tree_level_max") > 0) {
633 config[
"tree_level_max"].get_property(level_max);
635 long long use_osr = 1;
636 if (
config.count(
"use_osr") > 0) {
637 config[
"use_osr"].get_property(use_osr);
639 long long use_graph_search = 1;
640 if (
config.count(
"use_graph_search") > 0) {
641 config[
"use_graph_search"].get_property(use_graph_search);
644 long long stop_first_solution = 1;
645 if (
config.count(
"stop_first_solution") > 0) {
646 config[
"stop_first_solution"].get_property(stop_first_solution);
649 level_limit = std::min(std::max((
int)level_max, 0), 14);
652 std::string error(
"please increase level limit");
657 std::vector<GrayCodeCNOT> all_solutions;
658 if (use_graph_search) {
665 std::sort(all_cuts.begin(), all_cuts.end(), [](
const std::vector<int>& a,
const std::vector<int>& b){
666 if (a.size() != b.size())
return a.size() < b.size();
667 return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
669 std::map<std::pair<int, int>, std::vector<int>> pair_affects;
671 std::vector<int> cuts;
672 for (
size_t i = 0; i < all_cuts.size(); ++i) {
673 const std::vector<int>& A = all_cuts[i];
674 if ((std::find(A.begin(), A.end(), pair[0]) != A.end()) ^
675 (std::find(A.begin(), A.end(), pair[1]) != A.end())) {
676 cuts.push_back(static_cast<int>(i));
679 pair_affects[std::pair<int, int>(pair[0], pair[1])] = std::move(cuts);
687 all_solutions.emplace_back();
691 all_solutions.insert(all_solutions.end(), result.
solutions.begin(), result.
solutions.end());
695 if (stop_first_solution && all_solutions.size() > 0) {
703 best_solution = gcode;
715 if (use_osr && all_solutions.empty() && !ci.
prefixes.empty()) {
716 all_solutions.reserve(ci.
prefixes.size());
717 for (std::map<GrayCodeCNOT, SearchNode>::const_iterator it = ci.
prefixes.begin(); it != ci.
prefixes.end(); ++it) {
718 all_solutions.emplace_back(it->first.copy());
721 std::stringstream sstream;
722 sstream <<
"OSR did not find a fully disentangled solution; evaluating best prefix candidates with Hilbert-Schmidt optimization." << std::endl;
726 if (use_osr || use_graph_search) {
728 std::uniform_real_distribution<> distrib_real(0.0, 2 *
M_PI);
729 std::vector<double> optimized_parameters;
731 if (all_solutions.size() == 0) {
733 all_solutions.emplace_back(best_solution.
copy());
736 std::unique_ptr<Gates_block> gate_structure_loc;
742 double current_minimum_tmp;
743 for (
int iter = 0; iter < 5; iter++) {
745 for (
size_t idx = 0; idx < optimized_parameters.size(); idx++) {
746 optimized_parameters[idx] = distrib_real(
gen);
749 static_cast<int>(optimized_parameters.size()));
752 if (current_minimum_tmp < optimization_tolerance_loc) {
760 best_solution = solution;
762 if (
current_minimum < optimization_tolerance_loc && stop_first_solution) {
769 std::stringstream sstream;
770 sstream <<
"Decomposition did not reach prescribed high numerical precision." << std::endl;
779 std::vector<std::vector<int>>& all_cuts,
double Fnorm,
double osr_tol,
780 std::uniform_real_distribution<>& distrib_real, std::mt19937&
gen,
783 std::unique_ptr<Gates_block> gate_structure_loc(
787 std::vector<double> optimized_parameters(cDecomp_custom_random.
get_parameter_num());
788 for (
size_t idx = 0; idx < optimized_parameters.size(); idx++) {
789 optimized_parameters[idx] = distrib_real(gen);
792 static_cast<int>(optimized_parameters.size()));
796 for (
const std::vector<int>& cut : all_cuts) {
797 if (cut.size() != 1)
continue;
798 int max_rank = 2*(
int)std::min(cut.size(),
qbit_num-cut.size());
800 std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>> rank_result;
809 cDecomp_custom_random.
apply_to(params_float, U_float);
813 cDecomp_custom_random.
apply_to(params, U);
815 std::vector<std::pair<int, double>> osr_result;
816 osr_result.reserve(all_cuts.size());
818 for (
const std::vector<int>& eval_cut : all_cuts) {
825 if (cut == eval_cut) newrank = osr_result.back().first;
828 double best_kappa = std::numeric_limits<double>::infinity();
829 std::vector<int> best_edge_counts;
830 int min_cnots = osr_bound_solver.
solve_min_cnots(osr_result, best_kappa, best_edge_counts);
831 if (newrank <= rank || rank == max_rank-1)
832 rank_result = std::make_tuple(min_cnots, best_kappa, std::move(best_edge_counts), std::move(osr_result));
833 if (newrank > rank)
break;
834 rank = std::min(rank, newrank);
836 ev_results.
osr_results.emplace_back(std::move(rank_result));
844 const int m =
static_cast<int>(ops.
size());
845 std::vector<uint32_t> pred_mask(m, 0);
847 std::unordered_map<int,int> last_on;
848 last_on.reserve(m * 2);
850 for (
int k = 0;
k < m; ++
k) {
854 for (
int q : {a, b}) {
855 std::unordered_map<int,int>::iterator it = last_on.find(q);
856 if (it != last_on.end()) {
857 int prev = it->second;
858 pred_mask[
k] |= (1u << prev);
861 last_on.emplace(q,
k);
874 const int m =
static_cast<int>(smallpath.
size());
875 if (m == 0)
return true;
878 throw std::runtime_error(
"pattern too large for uint32_t mask");
881 const uint32_t FULL = (1u << m) - 1u;
885 std::vector<char> reachable(
size_t(1) << m, 0), next_reachable(
size_t(1) << m, 0);
888 for (
int i = 0; i < bigpath.
size(); i++) {
890 next_reachable = reachable;
892 for (uint32_t
S = 0;
S <= FULL; ++
S) {
893 if (!reachable[
S])
continue;
896 for (
int u = 0; u < m; ++u) {
897 uint32_t bit = 1u << u;
898 if (S & bit)
continue;
901 if ((pred_mask[u] & ~S) != 0)
continue;
904 if (smallpath[u] != b)
continue;
906 next_reachable[S | bit] = 1;
910 reachable.swap(next_reachable);
912 if (reachable[FULL])
return true;
915 return reachable[FULL];
946 patterns.begin(), patterns.end(),
953 patterns.push_back(path);
959 std::sort(all_cuts.begin(), all_cuts.end(), [](
const std::vector<int>& a,
const std::vector<int>& b){
960 if (a.size() != b.size())
return a.size() < b.size();
961 return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
964 double Fnorm = std::sqrt(static_cast<double>(1 <<
qbit_num));
965 double osr_tol = 1e-3;
968 std::unique_ptr<SearchNode> top_heap;
969 std::set<GrayCodeCNOT> visited;
974 std::uniform_real_distribution<> distrib_real(0.0, 2 *
M_PI);
976 std::function<bool(const GrayCodeCNOT&)> add_to_heap = [&](
const GrayCodeCNOT& path) ->
bool {
980 bool inserted = visited.insert(path).second;
1002 if (top_heap ==
nullptr || !(*top_heap < sn)) {
1003 top_heap.reset(
new SearchNode(std::move(sn)));
1011 add_to_heap(startpath);
1013 std::vector<int> full_topo_filter(
topology.size());
1014 std::iota(full_topo_filter.begin(), full_topo_filter.end(), 0);
1016 while (top_heap !=
nullptr) {
1017 std::unique_ptr<SearchNode> cur(top_heap.release());
1019 if (cur->get_min_cnots() == 0) {
1022 const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& cur_best_osr_result = cur->get_best_osr_result();
1023 const std::vector<int>& best_edge_counts = std::get<2>(cur_best_osr_result);
1024 std::vector<int> topo_filter;
1025 bool exact_edges =
false;
1029 topo_filter.resize(
topology.size());
1030 std::iota(topo_filter.begin(), topo_filter.end(), 0);
1031 std::sort(topo_filter.begin(), topo_filter.end(), [&](
int a,
int b){
1032 return best_edge_counts[a] > best_edge_counts[b];
1035 num_cnot = std::get<0>(cur_best_osr_result);
1036 topo_filter.reserve(std::get<0>(cur_best_osr_result));
1038 for (
size_t i = 0; i < best_edge_counts.size(); i++) {
1039 for (
int j = 0; j < best_edge_counts[i]; j++) {
1040 topo_filter.push_back(static_cast<int>(i));
1053 if (add_to_heap(newpath)) {
1055 return top_heap->get_min_cnots() == 0;
1061 if (*cur > *top_heap || num_cnot == std::get<0>(cur_best_osr_result)) {
1110 tbb::spin_mutex tree_search_mutex;
1112 std::vector<std::vector<int>>& all_cuts = ci.
all_cuts;
1114 std::map<GrayCodeCNOT, SearchNode>& prefixes = ci.
prefixes;
1116 double optimization_tolerance_loc;
1117 if (
config.count(
"optimization_tolerance") > 0) {
1118 config[
"optimization_tolerance"].get_property(optimization_tolerance_loc);
1122 long long stop_first_solution = 1;
1123 if (
config.count(
"stop_first_solution") > 0) {
1124 config[
"stop_first_solution"].get_property(stop_first_solution);
1127 volatile bool found_optimal_solution =
false;
1131 const std::set<std::vector<int>>& visited = level_result.
visited;
1133 const std::vector<std::pair<std::vector<int>,
GrayCodeCNOT>>& out_res = level_result.
out_res;
1135 std::set<GrayCodeCNOT> pairs_reduced;
1136 for (
const std::pair<std::vector<int>,
GrayCodeCNOT>& item : out_res) {
1137 pairs_reduced.insert(item.second);
1139 std::vector<GrayCodeCNOT> all_pairs(pairs_reduced.begin(), pairs_reduced.end());
1140 std::set<SearchNode> all_osr_results;
1141 int64_t iteration_max = all_pairs.size();
1142 std::vector<GrayCodeCNOT> successful_solutions;
1143 double Fnorm = std::sqrt(static_cast<double>(1 <<
qbit_num));
1144 double osr_tol = 1e-3;
1147 unsigned int nthreads = std::thread::hardware_concurrency();
1148 int64_t concurrency = (int64_t)nthreads;
1149 concurrency = concurrency < iteration_max ? concurrency : iteration_max;
1150 std::uniform_real_distribution<> distrib_real(0.0, 2 *
M_PI);
1154 auto process_job_range = [&](int64_t begin, int64_t end) {
1157 std::mt19937 ts_gen(std::random_device{}());
1159 for (int64_t job_idx = begin; job_idx < end; ++job_idx) {
1164 int64_t work_batch = iteration_max / concurrency;
1165 int64_t initial_offset = job_idx * work_batch;
1166 int64_t offset_max = (job_idx + 1) * work_batch - 1;
1168 if (job_idx == concurrency - 1) {
1169 offset_max = iteration_max - 1;
1175 for (int64_t iter_idx = initial_offset; iter_idx < offset_max + 1; iter_idx++) {
1176 if (stop_first_solution && found_optimal_solution) {
1181 SearchNode sn =
evaluate_path(cDecomp_custom_random, osr_bound_solver, all_cuts, Fnorm, osr_tol, distrib_real, ts_gen, solution);
1183 cDecomp_custom_random
1186 const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& osr_result = sn.
get_best_osr_result();
1187 bool isWorse =
false;
1188 for (
int idx = 0; idx < solution.
size(); idx++) {
1190 std::map<GrayCodeCNOT, SearchNode>::const_iterator prefix_it = prefixes.find(prefix);
1191 if (prefix_it == prefixes.end()) {
1196 const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& prefix_osr_result = prefix_it->second.get_best_osr_result();
1197 if (std::get<0>(osr_result) > std::get<0>(prefix_osr_result) ||
1198 (std::get<0>(osr_result) == std::get<0>(prefix_osr_result) &&
1199 std::get<1>(osr_result) + 1e-3 < std::get<1>(prefix_osr_result))) {
1204 int cnot_lower_bound = std::get<0>(osr_result);
1205 if (cnot_lower_bound <=
level_limit - level_num && !isWorse) {
1206 tbb::spin_mutex::scoped_lock tree_search_lock{tree_search_mutex};
1207 all_osr_results.emplace(std::move(sn));
1208 if (cnot_lower_bound == 0) {
1209 found_optimal_solution =
true;
1210 successful_solutions.push_back(solution.
copy());
1222 if (parallel == 0) {
1223 process_job_range(0, concurrency);
1226 int64_t work_batch = 1;
1229 tbb::blocked_range<int64_t>((int64_t)0, concurrency, work_batch), [&](tbb::blocked_range<int64_t> r) {
1230 process_job_range(r.begin(), r.end());
1234 long long beam_width = all_osr_results.size();
1235 if (
config.count(
"beam") > 0) {
1236 config[
"beam"].get_property(beam_width);
1237 if (beam_width <= 0) beam_width = all_osr_results.size();
1239 beam_width = std::min<long long>(beam_width, all_osr_results.size());
1240 std::map<GrayCodeCNOT, SearchNode> nextprefixes;
1241 for (std::set<SearchNode>::iterator item = all_osr_results.begin(); item != all_osr_results.end() && beam_width > 0; ++item, --beam_width) {
1242 nextprefixes.emplace(item->path, std::move(*item));
1244 std::vector<std::vector<int>> next_q;
1245 next_q.reserve(out_res.size());
1246 for (std::vector<std::pair<std::vector<int>,
GrayCodeCNOT>>::const_reverse_iterator it = out_res.crbegin();
1247 it != out_res.crend(); ++it) {
1248 if (nextprefixes.find(it->second) == nextprefixes.end()) {
1251 next_q.push_back(it->first);
1254 result.
solutions = std::move(successful_solutions);
1258 result.
prefixes = std::move(nextprefixes);
1270 tbb::spin_mutex tree_search_mutex;
1272 double optimization_tolerance_loc;
1273 if (
config.count(
"optimization_tolerance") > 0) {
1274 config[
"optimization_tolerance"].get_property(optimization_tolerance_loc);
1279 if (level_num == 0) {
1285 std::stringstream sstream;
1286 sstream <<
"Starting optimization with " << gate_structure_loc->
get_gate_num() <<
" decomposing layers." 1297 sstream <<
"Optimization with " << level_num <<
" levels converged to " << current_minimum_tmp;
1307 delete (gate_structure_loc);
1312 bool found_optimal_solution =
false;
1316 int n_ary_limit_max =
static_cast<int>(
topology.size());
1318 memset(n_ary_limits_int8.
get_data(), n_ary_limit_max, n_ary_limits_int8.
size() *
sizeof(int8_t));
1320 memset(n_ary_limits.
get_data(), n_ary_limit_max, n_ary_limits.
size() *
sizeof(
int));
1322 for (
int idx = 0; idx < n_ary_limits.
size(); idx++) {
1323 n_ary_limits[idx] = n_ary_limit_max;
1324 n_ary_limits_int8[idx] = n_ary_limit_max;
1327 int64_t iteration_max =
1328 static_cast<int64_t
>(pow(static_cast<double>(n_ary_limit_max), static_cast<double>(level_num)));
1331 unsigned int nthreads = std::thread::hardware_concurrency();
1332 int64_t concurrency = (int64_t)nthreads;
1333 concurrency = concurrency < iteration_max ? concurrency : iteration_max;
1337 auto process_job_range = [&](int64_t begin, int64_t end) {
1338 for (int64_t job_idx = begin; job_idx < end; ++job_idx) {
1343 int64_t work_batch = iteration_max / concurrency;
1344 int64_t initial_offset = job_idx * work_batch;
1345 int64_t offset_max = (job_idx + 1) * work_batch - 1;
1347 if (job_idx == concurrency - 1) {
1348 offset_max = iteration_max - 1;
1355 n_ary_limits, initial_offset);
1359 for (int64_t iter_idx = initial_offset; iter_idx < offset_max + 1; iter_idx++) {
1361 if (found_optimal_solution) {
1367 [](
int val) {
return static_cast<int8_t
>(val); });
1375 std::stringstream sstream;
1376 sstream <<
"Starting optimization with " << gate_structure_loc->
get_gate_num()
1377 <<
" decomposing layers." << std::endl;
1382 delete (gate_structure_loc);
1383 gate_structure_loc = NULL;
1390 sstream <<
"Optimization with " << level_num <<
" levels converged to " << current_minimum_tmp;
1397 tbb::spin_mutex::scoped_lock tree_search_lock{tree_search_mutex};
1399 if (current_minimum_tmp <
current_minimum && !found_optimal_solution) {
1402 gcode_best_solution = gcode;
1408 if (
current_minimum < optimization_tolerance_loc && !found_optimal_solution) {
1409 found_optimal_solution =
true;
1421 int changed_index, value_prev, value;
1422 if (gcode_counter.
next(changed_index, value_prev, value)) {
1430 if (parallel == 0) {
1431 process_job_range(0, concurrency);
1434 int64_t work_batch = 1;
1437 tbb::blocked_range<int64_t>((int64_t)0, concurrency, work_batch), [&](tbb::blocked_range<int64_t> r) {
1438 process_job_range(r.begin(), r.end());
1442 return gcode_best_solution;
1452 double optimization_tolerance_loc;
1453 if (
config.count(
"optimization_tolerance") > 0) {
1454 config[
"optimization_tolerance"].get_property(optimization_tolerance_loc);
1461 cDecomp_custom_random =
1465 cDecomp_custom_random =
1468 if (gate_structure_loc !=
nullptr) {
1485 int max_inner_iterations_loc = 10000;
1486 if (gate_structure_loc !=
nullptr) {
1488 max_inner_iterations_loc =
static_cast<int>((double)param_num_loc / 852 * 10000000.0);
1494 int max_inner_iterations_loc = 2000;
1499 int max_inner_iterations_loc = 10000;
1503 if (gate_structure_loc !=
nullptr)
1505 return cDecomp_custom_random;
1522 for (
int gcode_idx = 0; gcode_idx < gcode.
size(); gcode_idx++) {
1536 for (
int gcode_idx = 0; gcode_idx < gcode.
size(); gcode_idx++) {
1546 return gate_structure_loc;
1559 std::string error(
"N_Qubit_Decomposition_Tree_Search::add_two_qubit_block: Label of control/target qubit " 1560 "should be less than the number of qubits in the register.");
1564 if (control_qbit == target_qbit) {
1566 "N_Qubit_Decomposition_Tree_Search::add_two_qubit_block: Target and control qubits should be different");
1579 layer->
add_u3(target_qbit);
1580 layer->
add_u3(control_qbit);
1581 layer->
add_cnot(target_qbit, control_qbit);
1597 for (
int idx = 0; idx <
qbit_num; idx++) {
1607 if (gate_structure == NULL) {
1608 throw(
"N_Qubit_Decomposition_Tree_Search::add_finalyzing_layer: gate_structure is null pointer");
optimization_aglorithms alg
The optimization algorithm to be used in the optimization.
void set_osr_params(std::vector< std::vector< int >> use_cuts_in, int osr_rank_in, bool use_softmax_in)
void print(const std::stringstream &sstream, int verbose_level=1) const
Call to print output messages in the function of the verbosity level.
std::vector< std::tuple< int, double, std::vector< int >, std::vector< std::pair< int, double > > > > osr_results
Class to store single-precision real arrays and properties.
virtual Gates_block * determine_gate_structure(Matrix_real &optimized_parameters_mtx)
Call determine the gate structrue of the decomposing circuit.
Matrix_float to_float32() const
Convert to single precision.
std::map< GrayCodeCNOT, SearchNode > prefixes
Map from Gray code sequences to their OSR result pairs (rank, cost) for different cuts...
std::vector< std::vector< int > > unique_cuts(int n)
MinCnotBoundSolver osr_bound_solver
Map from CNOT pair (target, control) to the indices of cuts that are affected by this pair...
int get_num_iters()
Get the number of processed iterations during the optimization process.
void set_optimizer(optimization_aglorithms alg_in)
Call to set the optimizer engine to be used in solving the optimization problem.
void set_project_name(std::string &project_name_new)
Call to set the name of the project.
void insert_forbidden(const GrayCodeCNOT &path)
void add_two_qubit_block(Gates_block *gate_structure, int target_qbit, int control_qbit)
Call to add two-qubit building block (two single qubit rotation blocks and one two-qubit gate) to the...
void set_custom_gate_structure(Gates_block *gate_structure_in)
Call to set custom layers to the gate structure that are intended to be used in the subdecomposition...
std::vector< int > target_qbits
Vector of target qubit indices (for multi-qubit gates)
Matrix_real copy() const
Call to create a copy of the matrix.
int control_qbit
The index of the qubit which acts as a control qubit (control_qbit >= 0) in controlled operations...
std::set< std::vector< int > > visited
Set of visited states (represented as vectors of integers)
void add_gate(Gate *gate)
Append a general gate to the list of gates.
Matrix to_float64() const
Convert to double precision.
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
std::vector< std::pair< std::vector< int >, GrayCodeCNOT > > out_res
Vector of output results (discoveries) from the BFS level enumeration.
static int is_unique_structure(const GrayCodeCNOT &path, const std::vector< matrix_base< int >> &topology)
bool use_float
Selects float32 circuit application for parameter/unitary/state data.
cost_function_type cost_fnc
The chosen variant of the cost function.
N_Qubit_Decomposition_Tree_Search()
Nullary constructor of the class.
matrix_base< int > possible_target_qbits
List of possible target qubits according to the topology – paired up with possible control qubits...
GrayCodeCNOT tree_search_over_gate_structures(int level_num)
Call to perform tree search over possible gate structures.
std::vector< std::vector< int > > q
Queue of states to be processed in the next BFS level.
int target_qbit
The index of the qubit on which the operation acts (target_qbit >= 0)
scalar * data
pointer to the stored data
double get_current_minimum()
Call to get the obtained minimum of the cost function.
void release_gates()
Call to release the stored gates.
const std::vector< matrix_base< int > > & topology
void set_trace_offset(int trace_offset_in)
Set the trace offset used in the evaluation of the cost function.
int trace_offset
The offset in the first columns from which the "trace" is calculated. In this case Tr(A) = sum_(i-off...
Copyright 2021 Budapest Quantum Computing Group.
std::map< GrayCodeCNOT, SearchNode > prefixes
Map of GrayCodeCNOT to OSR (Operator Schmidt Rank) result pairs.
void add_cnot(int target_qbit, int control_qbit)
Append a CNOT gate gate to the list of gates.
void set_random_shift_count_max(int random_shift_count_max_in)
Call to set the maximal number of parameter randomization tries to escape a local minimum...
void increment_num_iters(int delta=1)
Atomically increment the tracked number of optimization iterations.
scalar * get_data() const
Call to get the pointer to the stored data.
void set_offset_max(const int64_t &value)
std::vector< uint32_t > build_pred_mask(const GrayCodeCNOT &ops, const std::vector< matrix_base< int >> &topology)
N_Qubit_Decomposition_custom perform_optimization(Gates_block *gate_structure_loc)
Call to perform the optimization on the given gate structure.
int level_limit
The maximal number of adaptive layers used in the decomposition.
int next()
Iterate the counter to the next value.
void sync_optimized_parameters_float()
Synchronize the float32 parameter mirror from the double optimizer storage.
int get_gate_num()
Call to get the number of gates grouped in the class.
static int canonical_prefix_ok(const GrayCodeCNOT &path, const std::vector< matrix_base< int >> &topology)
std::vector< std::pair< std::vector< int >, GrayCodeCNOT > > Discovery
std::vector< std::vector< int > > all_cuts
Vector of all possible qubit cuts, where each cut is represented as a vector of qubit indices...
int max_outer_iterations
Maximal number of iterations allowed in the optimization process.
std::string project_name
the name of the project
void set_max_inner_iterations(int max_inner_iterations_in)
Call to set the maximal number of iterations for which an optimization engine tries to solve the opti...
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
double optimization_tolerance
The maximal allowed error of the optimization problem (The error of the decomposition would scale wit...
GrayCode_base remove_Digit(const int idx) const
Call to add a new digit to the Gray code.
int accelerator_num
number of utilized accelerators
std::vector< matrix_base< int > > topology
A vector of index pairs encoding the connectivity between the qubits.
matrix_base< int > possible_control_qbits
List of possible control qubits according to the topology – paired up with possible target qubits...
std::pair< int, double > operator_schmidt_rank(const Matrix &U, int n, const std::vector< int > &A_qubits, double Fnorm, double tol=1e-10)
void set_debugfile(std::string debugfile)
Call to set the debugfile name.
static LevelResult enumerate_unordered_cnot_BFS_level_init(int n)
Initialize the breadth-first search (BFS) enumeration at depth 0 (identity state only).
Matrix_float Umtx_float
Float32 copy of the unitary used when config["use_float"] is true.
GrayCodeCNOT tree_search_over_gate_structures_best_first()
Matrix_real get_optimized_parameters()
Call to get the optimized parameters.
virtual void add_finalyzing_layer()
Call to add further layer to the gate structure used in the subdecomposition.
Structure containing the result of tree search over gate structures with Gray code and level informat...
void add_u3(int target_qbit)
Append a U3 gate to the list of gates.
std::vector< GrayCodeCNOT > patterns
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
LevelInfo level_info
Updated LevelInfo containing visited states and sequence pairs.
Header file for a class implementing the adaptive gate decomposition algorithm of arXiv:2203...
void combine(Gates_block *op_block)
Call to append the gates of an gate block to the current block.
GrayCode_base add_Digit(const intType n_ary_limit) const
Call to add a new digit to the Gray code.
std::set< std::vector< int > > visited
Set of visited states (represented as vectors of integers)
Structure containing level information for breadth-first search over gate structures.
void set_optimized_parameters(double *parameters, int num_of_parameters)
Call to set the optimized parameters for initial optimization.
std::map< std::vector< int >, GrayCodeCNOT > seq_pairs_of
Map from state vectors to their corresponding Gray code sequences.
void generate_insertions(const GrayCodeCNOT &curpath, const std::vector< matrix_base< int >> &topology, const std::vector< int > &topo_filt, int num_cnot, Callback &&callback)
virtual void apply_to(Matrix_real ¶meters_mtx, Matrix &input, int parallel=0) override
Call to apply the gate on the input array/matrix Gates_block*input.
int num_threads
Store the number of OpenMP threads. (During the calculations OpenMP multithreading is turned off...
SearchNode evaluate_path(N_Qubit_Decomposition_custom &cDecomp_custom_random, MinCnotBoundSolver &osr_bound_solver, std::vector< std::vector< int >> &all_cuts, double Fnorm, double osr_tol, std::uniform_real_distribution<> &distrib_real, std::mt19937 &gen, const GrayCodeCNOT &path)
int solve_min_cnots(const std::vector< std::pair< int, double > > &cut_bounds, int max_total=-1) const
void generate_insertions_recursive(const GrayCodeCNOT &curpath, const std::vector< matrix_base< int >> &topology, const std::vector< int > &topo_filt, int num_cnot, std::vector< int > &places, std::vector< int > &pairs, int depth, int min_place, Callback &&callback, bool &early_stop)
int verbose
Set the verbosity level of the output messages.
Matrix copy() const
Call to create a copy of the matrix.
void set_optimization_tolerance(double tolerance_in)
Call to set the tolerance of the optimization processes.
void copy_to(Matrix_real &target) const
Copy the matrix to a reusable double-precision target matrix.
Double-precision complex matrix (float64).
void copy_to(matrix_base< scalar > &target) const
Copy the current matrix storage into a reusable target matrix.
TreeSearchResult tree_search_over_gate_structures_osr(int level_num, LevelInfo &li, CutInfo &ci)
Perform tree search over possible gate structures using Gray code enumeration and Operator Schmidt Ra...
int size() const
Call to get the number of the allocated elements.
std::vector< int > control_qbits
Vector of control qubit indices (for multi-qubit gates)
GrayCode_base copy() const
Call to create a copy of the state.
Gates_block()
Default constructor of the class.
A class responsible for grouping two-qubit (CNOT,CZ,CH) and one-qubit gates into layers.
void omp_set_num_threads(int num_threads)
Set the number of threads on runtime in MKL.
virtual void start_decomposition()
Start the disentanglig process of the unitary.
GrayCode get()
Get the current gray code counter value.
Single-precision complex matrix (float32).
void set_verbose(int verbose_in)
Call to set the verbose attribute.
std::map< std::string, Config_Element > config
config metadata utilized during the optimization
dictionary gate_structure
Gates_block * construct_gate_structure_from_Gray_code(const GrayCodeCNOT &gcode, bool finalize=true)
Call to construct a gate structure corresponding to the configuration of the two-qubit gates describe...
bool contains_forbidden_subsequence(const GrayCodeCNOT &candidate) const
virtual void start_decomposition()
Start the disentanglig process of the unitary.
ForbiddenSubseqSet(const std::vector< matrix_base< int >> &topology)
std::vector< GrayCodeCNOT > solutions
Vector of successful Gray-code solutions.
void set_unitary(Matrix &Umtx_new)
Set unitary matrix.
const std::tuple< int, double, std::vector< int >, std::vector< std::pair< int, double > > > & get_best_osr_result() const
Header file for the paralleized calculation of the cost function of the final optimization problem (s...
static LevelResult enumerate_unordered_cnot_BFS_level_step(LevelInfo &L, const std::vector< matrix_base< int >> &topology, bool use_gl=true)
Perform one expansion level of breadth-first search (BFS) enumeration over CNOT gate structures...
volatile double current_minimum
The current minimum of the optimization problem.
Matrix Umtx
The unitary to be decomposed.
void export_gate_list_to_binary(Matrix_real ¶meters, Gates_block *gates_block, const std::string &filename, int verbosity)
Use to export a quantum circuit into binary format.
Structure containing cut information for operator Schmidt rank (OSR) analysis.
void export_unitary(std::string &filename)
exports unitary matrix to binary file
bool contains_topological_subsequence(const GrayCodeCNOT &smallpath, const GrayCodeCNOT &bigpath, const std::vector< matrix_base< int >> &topology)
int qbit_num
number of qubits spanning the matrix of the operation
void set_max_iteration(int max_outer_iterations_in)
Call to set the maximal number of the iterations in the optimization process.
double decomposition_error
error of the final decomposition
int max_inner_iterations
the maximal number of iterations for which an optimization engine tries to solve the optimization pro...
Matrix_real optimized_parameters_mtx
The optimized parameters for the gates.
int get_parallel_configuration()
Get the parallel configuration from the config.
void set_cost_function_variant(cost_function_type variant)
Call to set the variant of the cost function used in the calculations.
int get_parameter_num() override
Call to get the number of free parameters.
Matrix_float copy() const
Call to create a copy of the matrix.
virtual ~N_Qubit_Decomposition_Tree_Search()
Destructor of the class.
Structure containing the result of a BFS level enumeration.
void set_optimization_blocks(int optimization_block_in)
Call to set the number of gate blocks to be optimized in one shot.
Class to store data of complex arrays and its properties.
std::map< std::vector< int >, GrayCodeCNOT > seq_pairs_of
Map from state vectors to their corresponding Gray code sequences.
std::mt19937 gen
Standard mersenne_twister_engine seeded with rd()
int omp_get_max_threads()
get the number of threads in MKL