39 #include <unordered_map> 75 std::vector<int> I(n, 0);
76 for (
int i = 0; i <
n; ++i)
78 std::set<std::vector<int>>
visited;
87 result.
visited = std::move(visited);
89 result.
out_res = std::move(out_res);
97 const int m =
static_cast<int>(path.
size());
102 std::vector<std::vector<int>> succ(m);
103 std::vector<int> indeg(m, 0);
104 std::unordered_map<int, int> last_on;
105 last_on.reserve(m * 2);
107 for (
int k = 0;
k < m; ++
k) {
110 for (
int q : {a, b}) {
111 std::unordered_map<int, int>::iterator it = last_on.find(q);
112 if (it != last_on.end()) {
113 int prev = it->second;
114 succ[prev].push_back(
k);
118 last_on.emplace(q,
k);
125 std::pair<int, int> p;
129 bool operator()(
const Node& a,
const Node& b)
const {
132 return a.idx > b.idx;
135 std::priority_queue<Node, std::vector<Node>, Cmp> pq;
136 for (
int k = 0;
k < m; ++
k)
141 for (
int pos = 0; pos < m; ++pos) {
149 for (
int v : succ[u.idx]) {
158 for (
int idx = 0; idx < path.
size() - 3; idx++) {
159 if (path.
data[idx] == path.
data[idx + 1] && path.
data[idx] == path.
data[idx + 2] && path.
data[idx] == path.
data[idx + 3]) {
196 bool use_gl =
true) {
199 std::vector<std::vector<int>>& q = L.
q;
200 std::map<std::vector<int>,
GrayCodeCNOT> new_seq_pairs_of;
204 std::vector<int> A = q.back();
212 std::pair<int, int> m2 = {topology[p][1], topology[p][0]};
215 if (last_pairs.
size() >= 3 &&
216 std::all_of(last_pairs.
data + last_pairs.
size() - 3, last_pairs.
data + last_pairs.
size(),
217 [p](
const int&
x) {
return x == p; }))
220 seqp[seqp.
size() - 1] = p;
225 std::vector<std::pair<int, int>> allmv =
226 use_gl ? std::vector<std::pair<int, int>>{m1, m2} : std::vector<std::pair<int, int>>{m1};
228 for (std::pair<int, int> mv : allmv) {
232 if (mv.first != mv.second) {
233 B[mv.second] ^= B[mv.first];
236 if (visited.find(B) != visited.end()) {
240 B = std::vector<int>(last_pairs.
data, last_pairs.
data + last_pairs.
size());
247 seqp[seqp.
size() - 1] = p;
249 new_seq_pairs_of.emplace(B, std::move(seqp));
253 out_res.emplace_back(std::move(B), ref_pairs);
258 result.
visited = std::move(visited);
260 result.
out_res = std::move(out_res);
265 template <
class Callback>
269 const std::vector<int>& topo_filt,
271 std::vector<int>& places,
272 std::vector<int>& pairs,
278 const int nslots = curpath.
size() + 1;
280 if (depth == num_cnot) {
286 for (
int slot = 0; slot < nslots; ++slot) {
287 while (j < num_cnot && places[j] == slot) {
288 if (
k > 2 && out[
k-1] == pairs[j] && out[
k-2] == pairs[j] && out[
k-3] == pairs[j]) {
294 if (slot < curpath.
size()) {
295 if (
k > 2 && out[
k-1] == curpath[slot] && out[
k-2] == curpath[slot] && out[
k-3] == curpath[slot]) {
298 out[
k++] = curpath[slot];
301 early_stop |= callback(out);
304 uint32_t used_mask = 0u;
305 for (
int d = 0; d < depth; d++) {
308 for (
int place = min_place; place < nslots; ++place) {
309 if (depth != 0 && places[depth-1]+1 < place) {
312 places[depth] = place;
313 for (
int topo_idx : topo_filt) {
314 uint32_t edge_mask = (1u<<
topology[topo_idx][0]) | (1u<<
topology[topo_idx][1]);
315 if (depth != 0 && (used_mask & edge_mask) == 0)
continue;
316 pairs[depth] = topo_idx;
318 curpath,
topology, topo_filt, num_cnot,
319 places, pairs, depth + 1, place,
320 callback, early_stop);
321 if (early_stop)
return;
326 template <
class Callback>
330 const std::vector<int>& topo_filt,
334 std::vector<int> places(num_cnot);
335 std::vector<int> pairs(num_cnot);
336 bool early_stop =
false;
338 curpath,
topology, topo_filt, num_cnot,
340 std::forward<Callback>(callback), early_stop);
376 std::map<std::string, Config_Element>&
config,
384 std::map<std::string, Config_Element>&
config,
399 std::map<std::string, Config_Element>&
config,
413 for (
int qbit1 = 0; qbit1 <
qbit_num; qbit1++) {
414 for (
int qbit2 = qbit1 + 1; qbit2 <
qbit_num; qbit2++) {
423 for (
size_t idx = 0; idx <
topology.size(); idx++) {
425 std::string error(
"invalid topology: each element should be a pair of integers");
429 std::string error(
"invalid topology: qubit indices should be between 0 and qbit_num-1");
433 std::string error(
"invalid topology: target and control qubits should be different");
444 int n_ary_limit_max =
static_cast<int>(
topology.size());
448 for (
int element_idx = 0; element_idx < n_ary_limit_max; element_idx++) {
452 possible_control_qbits[element_idx] = edge[1];
475 std::map<std::string, Config_Element>&
config,
489 for (
int qbit1 = 0; qbit1 <
qbit_num; qbit1++) {
490 for (
int qbit2 = qbit1 + 1; qbit2 <
qbit_num; qbit2++) {
499 for (
size_t idx = 0; idx <
topology.size(); idx++) {
501 std::string error(
"invalid topology: each element should be a pair of integers");
505 std::string error(
"invalid topology: qubit indices should be between 0 and qbit_num-1");
509 std::string error(
"invalid topology: target and control qubits should be different");
520 int n_ary_limit_max =
static_cast<int>(
topology.size());
524 for (
int element_idx = 0; element_idx < n_ary_limit_max; element_idx++) {
528 possible_control_qbits[element_idx] = edge[1];
559 std::stringstream sstream;
560 sstream <<
"***************************************************************" << std::endl;
561 sstream <<
"Starting to disentangle " <<
qbit_num <<
"-qubit matrix" << std::endl;
562 sstream <<
"***************************************************************" << std::endl << std::endl << std::endl;
567 #if BLAS == 0 // undefined BLAS 570 #elif BLAS == 1 // MKL 572 MKL_Set_Num_Threads(1);
573 #elif BLAS == 2 // OpenBLAS 575 openblas_set_num_threads(1);
580 long long export_circuit_2_binary_loc;
581 if (
config.count(
"export_circuit_2_binary") > 0) {
582 config[
"export_circuit_2_binary"].get_property(export_circuit_2_binary_loc);
584 export_circuit_2_binary_loc = 0;
587 if (export_circuit_2_binary_loc > 0) {
588 std::string
filename(
"circuit_squander.binary");
594 std::string unitaryname(
"unitary_squander.binary");
604 delete (gate_structure_loc);
608 #if BLAS == 0 // undefined BLAS 610 #elif BLAS == 1 // MKL 612 #elif BLAS == 2 // OpenBLAS 624 double optimization_tolerance_loc;
625 long long level_max = 14;
626 if (
config.count(
"optimization_tolerance") > 0) {
627 config[
"optimization_tolerance"].get_property(optimization_tolerance_loc);
633 if (
config.count(
"tree_level_max") > 0) {
634 config[
"tree_level_max"].get_property(level_max);
636 long long use_osr = 1;
637 if (
config.count(
"use_osr") > 0) {
638 config[
"use_osr"].get_property(use_osr);
640 long long use_graph_search = 1;
641 if (
config.count(
"use_graph_search") > 0) {
642 config[
"use_graph_search"].get_property(use_graph_search);
645 long long stop_first_solution = 1;
646 if (
config.count(
"stop_first_solution") > 0) {
647 config[
"stop_first_solution"].get_property(stop_first_solution);
650 level_limit = std::min(std::max((
int)level_max, 0), 14);
653 std::string error(
"please increase level limit");
658 std::vector<GrayCodeCNOT> all_solutions;
659 if (use_graph_search) {
666 std::sort(all_cuts.begin(), all_cuts.end(), [](
const std::vector<int>& a,
const std::vector<int>& b){
667 if (a.size() != b.size())
return a.size() < b.size();
668 return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
670 std::map<std::pair<int, int>, std::vector<int>> pair_affects;
672 std::vector<int> cuts;
673 for (
size_t i = 0; i < all_cuts.size(); ++i) {
674 const std::vector<int>& A = all_cuts[i];
675 if ((std::find(A.begin(), A.end(), pair[0]) != A.end()) ^
676 (std::find(A.begin(), A.end(), pair[1]) != A.end())) {
677 cuts.push_back(static_cast<int>(i));
680 pair_affects[std::pair<int, int>(pair[0], pair[1])] = std::move(cuts);
688 all_solutions.emplace_back();
692 all_solutions.insert(all_solutions.end(), result.
solutions.begin(), result.
solutions.end());
696 if (stop_first_solution && all_solutions.size() > 0) {
704 best_solution = gcode;
716 if (use_osr && all_solutions.empty() && !ci.
prefixes.empty()) {
717 all_solutions.reserve(ci.
prefixes.size());
718 for (std::map<GrayCodeCNOT, SearchNode>::const_iterator it = ci.
prefixes.begin(); it != ci.
prefixes.end(); ++it) {
719 all_solutions.emplace_back(it->first.copy());
722 std::stringstream sstream;
723 sstream <<
"OSR did not find a fully disentangled solution; evaluating best prefix candidates with Hilbert-Schmidt optimization." << std::endl;
727 if (use_osr || use_graph_search) {
729 std::uniform_real_distribution<> distrib_real(0.0, 2 *
M_PI);
730 std::vector<double> optimized_parameters;
732 if (all_solutions.size() == 0) {
734 all_solutions.emplace_back(best_solution.
copy());
737 std::unique_ptr<Gates_block> gate_structure_loc;
743 double current_minimum_tmp;
744 for (
int iter = 0; iter < 5; iter++) {
746 for (
size_t idx = 0; idx < optimized_parameters.size(); idx++) {
747 optimized_parameters[idx] = distrib_real(
gen);
750 static_cast<int>(optimized_parameters.size()));
753 if (current_minimum_tmp < optimization_tolerance_loc) {
761 best_solution = solution;
763 if (
current_minimum < optimization_tolerance_loc && stop_first_solution) {
770 std::stringstream sstream;
771 sstream <<
"Decomposition did not reach prescribed high numerical precision." << std::endl;
780 std::vector<std::vector<int>>& all_cuts,
double Fnorm,
double osr_tol,
781 std::uniform_real_distribution<>& distrib_real, std::mt19937&
gen,
784 std::unique_ptr<Gates_block> gate_structure_loc(
788 std::vector<double> optimized_parameters(cDecomp_custom_random.
get_parameter_num());
789 for (
size_t idx = 0; idx < optimized_parameters.size(); idx++) {
790 optimized_parameters[idx] = distrib_real(gen);
793 static_cast<int>(optimized_parameters.size()));
797 for (
const std::vector<int>& cut : all_cuts) {
798 if (cut.size() != 1)
continue;
799 int max_rank = 2*(
int)std::min(cut.size(),
qbit_num-cut.size());
801 std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>> rank_result;
810 cDecomp_custom_random.
apply_to(params_float, U_float);
814 cDecomp_custom_random.
apply_to(params, U);
816 std::vector<std::pair<int, double>> osr_result;
817 osr_result.reserve(all_cuts.size());
819 for (
const std::vector<int>& eval_cut : all_cuts) {
826 if (cut == eval_cut) newrank = osr_result.back().first;
829 double best_kappa = std::numeric_limits<double>::infinity();
830 std::vector<int> best_edge_counts;
831 int min_cnots = osr_bound_solver.
solve_min_cnots(osr_result, best_kappa, best_edge_counts);
832 if (newrank <= rank || rank == max_rank-1)
833 rank_result = std::make_tuple(min_cnots, best_kappa, std::move(best_edge_counts), std::move(osr_result));
834 if (newrank > rank)
break;
835 rank = std::min(rank, newrank);
837 ev_results.
osr_results.emplace_back(std::move(rank_result));
845 const int m =
static_cast<int>(ops.
size());
846 std::vector<uint32_t> pred_mask(m, 0);
848 std::unordered_map<int,int> last_on;
849 last_on.reserve(m * 2);
851 for (
int k = 0;
k < m; ++
k) {
855 for (
int q : {a, b}) {
856 std::unordered_map<int,int>::iterator it = last_on.find(q);
857 if (it != last_on.end()) {
858 int prev = it->second;
859 pred_mask[
k] |= (1u << prev);
862 last_on.emplace(q,
k);
875 const int m =
static_cast<int>(smallpath.
size());
876 if (m == 0)
return true;
879 throw std::runtime_error(
"pattern too large for uint32_t mask");
882 const uint32_t FULL = (1u << m) - 1u;
886 std::vector<char> reachable(
size_t(1) << m, 0), next_reachable(
size_t(1) << m, 0);
889 for (
int i = 0; i < bigpath.
size(); i++) {
891 next_reachable = reachable;
893 for (uint32_t
S = 0;
S <= FULL; ++
S) {
894 if (!reachable[
S])
continue;
897 for (
int u = 0; u < m; ++u) {
898 uint32_t bit = 1u << u;
899 if (S & bit)
continue;
902 if ((pred_mask[u] & ~S) != 0)
continue;
905 if (smallpath[u] != b)
continue;
907 next_reachable[S | bit] = 1;
911 reachable.swap(next_reachable);
913 if (reachable[FULL])
return true;
916 return reachable[FULL];
947 patterns.begin(), patterns.end(),
954 patterns.push_back(path);
960 std::sort(all_cuts.begin(), all_cuts.end(), [](
const std::vector<int>& a,
const std::vector<int>& b){
961 if (a.size() != b.size())
return a.size() < b.size();
962 return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
965 double Fnorm = std::sqrt(static_cast<double>(1 <<
qbit_num));
966 double osr_tol = 1e-3;
969 std::unique_ptr<SearchNode> top_heap;
970 std::set<GrayCodeCNOT> visited;
975 std::uniform_real_distribution<> distrib_real(0.0, 2 *
M_PI);
977 std::function<bool(const GrayCodeCNOT&)> add_to_heap = [&](
const GrayCodeCNOT& path) ->
bool {
981 bool inserted = visited.insert(path).second;
1003 if (top_heap ==
nullptr || !(*top_heap < sn)) {
1004 top_heap.reset(
new SearchNode(std::move(sn)));
1012 add_to_heap(startpath);
1014 std::vector<int> full_topo_filter(
topology.size());
1015 std::iota(full_topo_filter.begin(), full_topo_filter.end(), 0);
1017 while (top_heap !=
nullptr) {
1018 std::unique_ptr<SearchNode> cur(top_heap.release());
1020 if (cur->get_min_cnots() == 0) {
1023 const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& cur_best_osr_result = cur->get_best_osr_result();
1024 const std::vector<int>& best_edge_counts = std::get<2>(cur_best_osr_result);
1025 std::vector<int> topo_filter;
1026 bool exact_edges =
false;
1030 topo_filter.resize(
topology.size());
1031 std::iota(topo_filter.begin(), topo_filter.end(), 0);
1032 std::sort(topo_filter.begin(), topo_filter.end(), [&](
int a,
int b){
1033 return best_edge_counts[a] > best_edge_counts[b];
1036 num_cnot = std::get<0>(cur_best_osr_result);
1037 topo_filter.reserve(std::get<0>(cur_best_osr_result));
1039 for (
size_t i = 0; i < best_edge_counts.size(); i++) {
1040 for (
int j = 0; j < best_edge_counts[i]; j++) {
1041 topo_filter.push_back(static_cast<int>(i));
1054 if (add_to_heap(newpath)) {
1056 return top_heap->get_min_cnots() == 0;
1062 if (*cur > *top_heap || num_cnot == std::get<0>(cur_best_osr_result)) {
1111 tbb::spin_mutex tree_search_mutex;
1113 std::vector<std::vector<int>>& all_cuts = ci.
all_cuts;
1115 std::map<GrayCodeCNOT, SearchNode>& prefixes = ci.
prefixes;
1117 double optimization_tolerance_loc;
1118 if (
config.count(
"optimization_tolerance") > 0) {
1119 config[
"optimization_tolerance"].get_property(optimization_tolerance_loc);
1123 long long stop_first_solution = 1;
1124 if (
config.count(
"stop_first_solution") > 0) {
1125 config[
"stop_first_solution"].get_property(stop_first_solution);
1128 std::atomic<bool> found_optimal_solution{
false};
1132 const std::set<std::vector<int>>& visited = level_result.
visited;
1134 const std::vector<std::pair<std::vector<int>,
GrayCodeCNOT>>& out_res = level_result.
out_res;
1136 std::set<GrayCodeCNOT> pairs_reduced;
1137 for (
const std::pair<std::vector<int>,
GrayCodeCNOT>& item : out_res) {
1138 pairs_reduced.insert(item.second);
1140 std::vector<GrayCodeCNOT> all_pairs(pairs_reduced.begin(), pairs_reduced.end());
1141 std::set<SearchNode> all_osr_results;
1142 int64_t iteration_max = all_pairs.size();
1143 std::vector<GrayCodeCNOT> successful_solutions;
1144 double Fnorm = std::sqrt(static_cast<double>(1 <<
qbit_num));
1145 double osr_tol = 1e-3;
1148 unsigned int nthreads = std::thread::hardware_concurrency();
1149 int64_t concurrency = (int64_t)nthreads;
1150 concurrency = concurrency < iteration_max ? concurrency : iteration_max;
1153 auto process_job_range = [&](int64_t begin, int64_t end) {
1156 std::mt19937 ts_gen(std::random_device{}());
1157 std::uniform_real_distribution<> distrib_real(0.0, 2 *
M_PI);
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 &&
1177 found_optimal_solution.load(std::memory_order_acquire)) {
1182 SearchNode sn =
evaluate_path(cDecomp_custom_random, osr_bound_solver, all_cuts, Fnorm, osr_tol, distrib_real, ts_gen, solution);
1184 cDecomp_custom_random
1187 const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& osr_result = sn.
get_best_osr_result();
1188 bool isWorse =
false;
1189 for (
int idx = 0; idx < solution.
size(); idx++) {
1191 std::map<GrayCodeCNOT, SearchNode>::const_iterator prefix_it = prefixes.find(prefix);
1192 if (prefix_it == prefixes.end()) {
1197 const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& prefix_osr_result = prefix_it->second.get_best_osr_result();
1198 if (std::get<0>(osr_result) > std::get<0>(prefix_osr_result) ||
1199 (std::get<0>(osr_result) == std::get<0>(prefix_osr_result) &&
1200 std::get<1>(osr_result) + 1e-3 < std::get<1>(prefix_osr_result))) {
1205 int cnot_lower_bound = std::get<0>(osr_result);
1206 if (cnot_lower_bound <=
level_limit - level_num && !isWorse) {
1207 tbb::spin_mutex::scoped_lock tree_search_lock{tree_search_mutex};
1208 all_osr_results.emplace(std::move(sn));
1209 if (cnot_lower_bound == 0) {
1210 found_optimal_solution.store(
true, std::memory_order_release);
1211 successful_solutions.push_back(solution.
copy());
1223 if (parallel == 0) {
1224 process_job_range(0, concurrency);
1227 int64_t work_batch = 1;
1230 tbb::blocked_range<int64_t>((int64_t)0, concurrency, work_batch), [&](tbb::blocked_range<int64_t> r) {
1231 process_job_range(r.begin(), r.end());
1235 long long beam_width = all_osr_results.size();
1236 if (
config.count(
"beam") > 0) {
1237 config[
"beam"].get_property(beam_width);
1238 if (beam_width <= 0) beam_width = all_osr_results.size();
1240 beam_width = std::min<long long>(beam_width, all_osr_results.size());
1241 std::map<GrayCodeCNOT, SearchNode> nextprefixes;
1242 for (std::set<SearchNode>::iterator item = all_osr_results.begin(); item != all_osr_results.end() && beam_width > 0; ++item, --beam_width) {
1243 nextprefixes.emplace(item->path, std::move(*item));
1245 std::vector<std::vector<int>> next_q;
1246 next_q.reserve(out_res.size());
1247 for (std::vector<std::pair<std::vector<int>,
GrayCodeCNOT>>::const_reverse_iterator it = out_res.crbegin();
1248 it != out_res.crend(); ++it) {
1249 if (nextprefixes.find(it->second) == nextprefixes.end()) {
1252 next_q.push_back(it->first);
1255 result.
solutions = std::move(successful_solutions);
1259 result.
prefixes = std::move(nextprefixes);
1271 tbb::spin_mutex tree_search_mutex;
1273 double optimization_tolerance_loc;
1274 if (
config.count(
"optimization_tolerance") > 0) {
1275 config[
"optimization_tolerance"].get_property(optimization_tolerance_loc);
1280 if (level_num == 0) {
1286 std::stringstream sstream;
1287 sstream <<
"Starting optimization with " << gate_structure_loc->
get_gate_num() <<
" decomposing layers." 1298 sstream <<
"Optimization with " << level_num <<
" levels converged to " << current_minimum_tmp;
1308 delete (gate_structure_loc);
1313 std::atomic<bool> found_optimal_solution{
false};
1317 int n_ary_limit_max =
static_cast<int>(
topology.size());
1319 memset(n_ary_limits_int8.
get_data(), n_ary_limit_max, n_ary_limits_int8.
size() *
sizeof(int8_t));
1321 memset(n_ary_limits.
get_data(), n_ary_limit_max, n_ary_limits.
size() *
sizeof(
int));
1323 for (
int idx = 0; idx < n_ary_limits.
size(); idx++) {
1324 n_ary_limits[idx] = n_ary_limit_max;
1325 n_ary_limits_int8[idx] = n_ary_limit_max;
1328 int64_t iteration_max =
1329 static_cast<int64_t
>(pow(static_cast<double>(n_ary_limit_max), static_cast<double>(level_num)));
1332 unsigned int nthreads = std::thread::hardware_concurrency();
1333 int64_t concurrency = (int64_t)nthreads;
1334 concurrency = concurrency < iteration_max ? concurrency : iteration_max;
1338 auto process_job_range = [&](int64_t begin, int64_t end) {
1339 for (int64_t job_idx = begin; job_idx < end; ++job_idx) {
1344 int64_t work_batch = iteration_max / concurrency;
1345 int64_t initial_offset = job_idx * work_batch;
1346 int64_t offset_max = (job_idx + 1) * work_batch - 1;
1348 if (job_idx == concurrency - 1) {
1349 offset_max = iteration_max - 1;
1356 n_ary_limits, initial_offset);
1360 for (int64_t iter_idx = initial_offset; iter_idx < offset_max + 1; iter_idx++) {
1362 if (found_optimal_solution.load(std::memory_order_acquire)) {
1368 [](
int val) {
return static_cast<int8_t
>(val); });
1376 std::stringstream sstream;
1377 sstream <<
"Starting optimization with " << gate_structure_loc->
get_gate_num()
1378 <<
" decomposing layers." << std::endl;
1383 delete (gate_structure_loc);
1384 gate_structure_loc = NULL;
1391 sstream <<
"Optimization with " << level_num <<
" levels converged to " << current_minimum_tmp;
1398 tbb::spin_mutex::scoped_lock tree_search_lock{tree_search_mutex};
1401 !found_optimal_solution.load(std::memory_order_relaxed)) {
1404 gcode_best_solution = gcode;
1411 !found_optimal_solution.load(std::memory_order_relaxed)) {
1412 found_optimal_solution.store(
true, std::memory_order_release);
1424 int changed_index, value_prev, value;
1425 if (gcode_counter.
next(changed_index, value_prev, value)) {
1433 if (parallel == 0) {
1434 process_job_range(0, concurrency);
1437 int64_t work_batch = 1;
1440 tbb::blocked_range<int64_t>((int64_t)0, concurrency, work_batch), [&](tbb::blocked_range<int64_t> r) {
1441 process_job_range(r.begin(), r.end());
1445 return gcode_best_solution;
1455 double optimization_tolerance_loc;
1456 if (
config.count(
"optimization_tolerance") > 0) {
1457 config[
"optimization_tolerance"].get_property(optimization_tolerance_loc);
1464 cDecomp_custom_random =
1468 cDecomp_custom_random =
1471 if (gate_structure_loc !=
nullptr) {
1488 int max_inner_iterations_loc = 10000;
1489 if (gate_structure_loc !=
nullptr) {
1491 max_inner_iterations_loc =
static_cast<int>((double)param_num_loc / 852 * 10000000.0);
1497 int max_inner_iterations_loc = 2000;
1502 int max_inner_iterations_loc = 10000;
1506 if (gate_structure_loc !=
nullptr)
1508 return cDecomp_custom_random;
1525 for (
int gcode_idx = 0; gcode_idx < gcode.
size(); gcode_idx++) {
1539 for (
int gcode_idx = 0; gcode_idx < gcode.
size(); gcode_idx++) {
1549 return gate_structure_loc;
1562 std::string error(
"N_Qubit_Decomposition_Tree_Search::add_two_qubit_block: Label of control/target qubit " 1563 "should be less than the number of qubits in the register.");
1567 if (control_qbit == target_qbit) {
1569 "N_Qubit_Decomposition_Tree_Search::add_two_qubit_block: Target and control qubits should be different");
1582 layer->
add_u3(target_qbit);
1583 layer->
add_u3(control_qbit);
1584 layer->
add_cnot(target_qbit, control_qbit);
1600 for (
int idx = 0; idx <
qbit_num; idx++) {
1610 if (gate_structure == NULL) {
1611 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