Sequential Quantum Gate Decomposer  v1.9.6
Powerful decomposition of general unitarias into one- and two-qubit gates gates
N_Qubit_Decomposition_Tree_Search.h
Go to the documentation of this file.
1 /*
2 Created on Fri Jun 26 14:13:26 2020
3 Copyright 2020 Peter Rakyta, Ph.D.
4 
5 Licensed under the Apache License, Version 2.0 (the "License");
6 you may not use this file except in compliance with the License.
7 You may obtain a copy of the License at
8 
9  http://www.apache.org/licenses/LICENSE-2.0
10 
11 Unless required by applicable law or agreed to in writing, software
12 distributed under the License is distributed on an "AS IS" BASIS,
13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 See the License for the specific language governing permissions and
15 limitations under the License.
16 
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see http://www.gnu.org/licenses/.
19 
20 @author: Peter Rakyta, Ph.D.
21 */
26 #ifndef N_Qubit_Decomposition_Tree_Search_H
27 #define N_Qubit_Decomposition_Tree_Search_H
28 #include "GrayCode.h"
30 
31 #include <map>
32 #include <numeric>
33 #include <set>
34 #include <tuple>
35 #include <utility>
36 #include <vector>
37 
39 
44 struct LevelInfo {
46  std::set<std::vector<int>> visited;
48  std::map<std::vector<int>, GrayCodeCNOT> seq_pairs_of;
50  std::vector<std::vector<int>> q;
51 };
52 
53 // topology: vector of pairs/qubit-edges, e.g. {(0,1), (0,2), ...}
54 // cuts: each cut is a vector<int> listing one side of the bipartition
55 // cut_bounds: required cross-cut counts for each cut
56 
58 public:
59  MinCnotBoundSolver(int num_qubits,
60  const std::vector<std::vector<int>>& cuts,
61  const std::vector<matrix_base<int>>& topology)
62  : num_qubits_(num_qubits), num_edges_(static_cast<int>(topology.size())), cuts_(cuts) {
63  build_cut_to_edges(topology);
64  }
65 
66  int solve_min_cnots(const std::vector<std::pair<int, double> >& cut_bounds, int max_total = -1) const {
67  // return std::max_element(cut_bounds.begin(), cut_bounds.end(),
68  // [](const std::pair<int, double>& a, const std::pair<int, double>& b) {
69  // return a.first < b.first;
70  // })->first;
71  if (cut_bounds.size() != cuts_.size()) {
72  throw std::invalid_argument("cut_bounds size must match cuts size");
73  }
74 
75  std::vector<int> edge_counts(num_edges_, 0);
76 
77  for (int total = 0;; ++total) {
78  if (max_total >= 0 && total > max_total) {
79  return -1; // not found within bound
80  }
81  if (feasible_for_some_composition(total, edge_counts, cut_bounds, 0, 0)) {
82  return total;
83  }
84  }
85  }
86  int solve_min_cnots(const std::vector<std::pair<int, double> >& cut_bounds, double& best_kappa, std::vector<int>& best_edge_counts, int max_total = -1) const {
87  if (cut_bounds.size() != cuts_.size()) {
88  throw std::invalid_argument("cut_bounds size must match cuts size");
89  }
90  best_edge_counts.clear();
91  best_kappa = std::numeric_limits<double>::infinity();
92 
93  std::vector<int> edge_counts(num_edges_, 0);
94 
95  for (int total = 0;; ++total) {
96  if (max_total >= 0 && total > max_total) {
97  return -1; // not found within bound
98  }
99  if (best_feasible_for_some_composition(total, edge_counts, cut_bounds, 0, 0, best_kappa, best_edge_counts)) {
100  return total;
101  }
102  }
103  }
104 private:
107  std::vector<std::vector<int>> cuts_;
108  std::vector<std::vector<int>> cut_to_edges_;
109 
110  void build_cut_to_edges(const std::vector<matrix_base<int>>& topology) {
111  cut_to_edges_.clear();
112  cut_to_edges_.reserve(cuts_.size());
113 
114  for (const std::vector<int>& cut : cuts_) {
115  std::vector<char> in_cut(num_qubits_, 0);
116  for (int q : cut) {
117  in_cut[q] = 1;
118  }
119 
120  std::vector<int> crossing_edges;
121  crossing_edges.reserve(topology.size());
122 
123  for (int i = 0; i < static_cast<int>(topology.size()); ++i) {
124  if (in_cut[topology[i][0]] != in_cut[topology[i][1]]) {
125  crossing_edges.push_back(i);
126  }
127  }
128  cut_to_edges_.push_back(std::move(crossing_edges));
129  }
130  }
131 
132  bool composition_satisfies(const std::vector<int>& edge_counts,
133  const std::vector<std::pair<int, double> >& cut_bounds) const {
134  for (int c = 0; c < static_cast<int>(cut_to_edges_.size()); ++c) {
135  const int bound = cut_bounds[c].first;
136  if (bound <= 0) continue;
137 
138  int sum = 0;
139  for (int edge_idx : cut_to_edges_[c]) {
140  sum += edge_counts[edge_idx];
141  if (sum >= bound) break;
142  }
143  if (sum < bound) {
144  return false;
145  }
146  }
147  return true;
148  }
149 
151  std::vector<int>& edge_counts,
152  const std::vector<std::pair<int, double> >& cut_bounds,
153  int pos,
154  int used_sum) const {
155  const int m = static_cast<int>(edge_counts.size());
156 
157  if (pos == m - 1) {
158  edge_counts[pos] = total - used_sum;
159  return composition_satisfies(edge_counts, cut_bounds);
160  }
161 
162  const int remaining = total - used_sum;
163  for (int x = 0; x <= remaining; ++x) {
164  edge_counts[pos] = x;
165  if (feasible_for_some_composition(total, edge_counts, cut_bounds, pos + 1, used_sum + x)) {
166  return true;
167  }
168  }
169  return false;
170  }
172  int total,
173  std::vector<int>& edge_counts,
174  const std::vector<std::pair<int, double>>& cut_bounds,
175  int pos,
176  int used_sum,
177  double& best_kappa,
178  std::vector<int>& best_edge_counts) const
179  {
180  const bool use_surplus = true; // whether to use surplus (coverage - bound) or just coverage for kappa objective
181  const int m = static_cast<int>(edge_counts.size());
182 
183  if (pos == m - 1) {
184  edge_counts[pos] = total - used_sum;
185 
186  if (!composition_satisfies(edge_counts, cut_bounds)) {
187  return false;
188  }
189 
190  // Secondary objective:
191  // minimize sum_c kappa_c * coverage_c
192  double kappa_obj = 0.0;
193  for (int c = 0; c < static_cast<int>(cut_to_edges_.size()); ++c) {
194  int coverage = 0;
195  for (int edge_idx : cut_to_edges_[c]) {
196  coverage += edge_counts[edge_idx];
197  }
198  if (use_surplus) {
199  const int surplus = coverage - cut_bounds[c].first;
200  kappa_obj += cut_bounds[c].second * static_cast<double>(surplus);
201  } else {
202  kappa_obj += cut_bounds[c].second * static_cast<double>(coverage);
203  }
204  }
205 
206  if (kappa_obj < best_kappa) {
207  best_kappa = kappa_obj;
208  best_edge_counts = edge_counts;
209  }
210  return true;
211  }
212 
213  bool found = false;
214  const int remaining = total - used_sum;
215  for (int x = 0; x <= remaining; ++x) {
216  edge_counts[pos] = x;
217  if (best_feasible_for_some_composition(
218  total, edge_counts, cut_bounds, pos + 1, used_sum + x,
219  best_kappa, best_edge_counts)) {
220  found = true;
221  }
222  }
223  return found;
224  }
225 };
226 
227 struct SearchNode {
228  std::vector<std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>> osr_results;
230  SearchNode(GrayCodeCNOT path) : path(path) {}
231  int get_min_cnots() const {
232  return std::get<0>(*std::min_element(osr_results.begin(), osr_results.end(),
233  [](const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& a, const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& b) {
234  return std::get<0>(a) < std::get<0>(b);
235  }));
236  }
237  const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& get_best_osr_result() const {
238  return *std::min_element(osr_results.begin(), osr_results.end(),
239  [](const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& a, const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& b) {
240  if (std::get<0>(a) != std::get<0>(b)) return std::get<0>(a) < std::get<0>(b);
241  if (std::get<1>(a) != std::get<1>(b)) return std::get<1>(a) < std::get<1>(b);
242  double a_sum = std::accumulate(std::get<3>(a).begin(), std::get<3>(a).end(), 0.0, [&a](double c, const std::pair<int, double>& d){ return c + d.first * std::get<3>(a).size() + d.second; });
243  double b_sum = std::accumulate(std::get<3>(b).begin(), std::get<3>(b).end(), 0.0, [&b](double c, const std::pair<int, double>& d){ return c + d.first * std::get<3>(b).size() + d.second; });
244  return a_sum < b_sum;
245  });
246  }
247  bool operator<(const SearchNode& other) const { return other > *this; }
248  bool operator>(const SearchNode& other) const {
249  // int min_cnots = get_min_cnots();
250  // int other_min_cnots = other.get_min_cnots();
251  // int tot_cnot = path.size() + min_cnots;
252  // int other_tot_cnot = other.path.size() + other_min_cnots;
253  // if (tot_cnot != other_tot_cnot)
254  // return tot_cnot > other_tot_cnot;
255  //if (min_cnots != other_min_cnots)
256  // return min_cnots > other_min_cnots;
257  const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& best_osr = get_best_osr_result();
258  const std::tuple<int, double, std::vector<int>, std::vector<std::pair<int, double>>>& other_best_osr = other.get_best_osr_result();
259  if (std::get<0>(best_osr) != std::get<0>(other_best_osr))
260  return std::get<0>(best_osr) > std::get<0>(other_best_osr);
261  if (std::get<1>(best_osr) != std::get<1>(other_best_osr))
262  return std::get<1>(best_osr) > std::get<1>(other_best_osr);
263  return std::accumulate(std::get<3>(best_osr).begin(), std::get<3>(best_osr).end(), 0.0, [&best_osr](double c, const std::pair<int, double>& d){ return c + d.first * std::get<3>(best_osr).size() + d.second; }) >
264  std::accumulate(std::get<3>(other_best_osr).begin(), std::get<3>(other_best_osr).end(), 0.0, [&other_best_osr](double c, const std::pair<int, double>& d){ return c + d.first * std::get<3>(other_best_osr).size() + d.second; });
265  /*int tot = 0;
266  for (size_t i = 0; i < osr_results.size(); i++) {
267  if (osr_results[i].first < other.osr_results[i].first) { tot -= 1; continue; }
268  if (osr_results[i].first > other.osr_results[i].first) { tot += 1; continue; }
269  if (ParetoFrontier::dominates(osr_results[i].second, other.osr_results[i].second)) { tot -= 1; continue; }
270  if (ParetoFrontier::dominates(other.osr_results[i].second, osr_results[i].second)) { tot += 1; continue; }
271  }
272  if (tot != 0) return tot > 0;
273  return std::accumulate(osr_results.begin(), osr_results.end(), 0.0,
274  [](double a, const std::pair<int, std::vector<std::pair<int, double>>>& b){
275  return a + std::accumulate(b.second.begin(), b.second.end(), 0.0, [](double c, const std::pair<int, double>& d){ return c + d.first + d.second; });
276  }) >
277  std::accumulate(other.osr_results.begin(), other.osr_results.end(), 0.0,
278  [](double a, const std::pair<int, std::vector<std::pair<int, double>>>& b){
279  return a + std::accumulate(b.second.begin(), b.second.end(), 0.0, [](double c, const std::pair<int, double>& d){ return c + d.first + d.second; });
280  });*/
281  }
282 };
283 
289 struct CutInfo {
291  std::vector<std::vector<int>> all_cuts;
295  std::map<GrayCodeCNOT, SearchNode> prefixes;
296  CutInfo(std::vector<std::vector<int>> all_cuts, MinCnotBoundSolver osr_bound_solver) : all_cuts(std::move(all_cuts)), osr_bound_solver(std::move(osr_bound_solver)) {}
297 };
298 
304  std::vector<GrayCodeCNOT> solutions;
308  std::map<GrayCodeCNOT, SearchNode> prefixes;
309 };
310 
316 
317  public:
318  protected:
324  std::vector<matrix_base<int>> topology;
325 
330 
331  public:
337 
347  N_Qubit_Decomposition_Tree_Search(Matrix Umtx_in, int qbit_num_in, std::map<std::string, Config_Element>& config,
348  int accelerator_num = 0);
349 
353  N_Qubit_Decomposition_Tree_Search(Matrix_float Umtx_in, int qbit_num_in, std::map<std::string, Config_Element>& config,
354  int accelerator_num = 0);
355 
366  N_Qubit_Decomposition_Tree_Search(Matrix Umtx_in, int qbit_num_in, std::vector<matrix_base<int>> topology_in,
367  std::map<std::string, Config_Element>& config, int accelerator_num = 0);
368 
372  N_Qubit_Decomposition_Tree_Search(Matrix_float Umtx_in, int qbit_num_in, std::vector<matrix_base<int>> topology_in,
373  std::map<std::string, Config_Element>& config, int accelerator_num = 0);
374 
379 
386  virtual void start_decomposition();
387 
392  virtual Gates_block* determine_gate_structure(Matrix_real& optimized_parameters_mtx);
393 
400  void add_two_qubit_block(Gates_block* gate_structure, int target_qbit, int control_qbit);
401 
408  Gates_block* construct_gate_structure_from_Gray_code(const GrayCodeCNOT& gcode, bool finalize = true);
409 
416  GrayCodeCNOT tree_search_over_gate_structures(int level_num);
417 
418  SearchNode evaluate_path(
419  N_Qubit_Decomposition_custom& cDecomp_custom_random, MinCnotBoundSolver& osr_bound_solver,
420  std::vector<std::vector<int>>& all_cuts, double Fnorm, double osr_tol,
421  std::uniform_real_distribution<>& distrib_real, std::mt19937& gen,
422  const GrayCodeCNOT& path);
423 
451  TreeSearchResult tree_search_over_gate_structures_osr(int level_num, LevelInfo& li, CutInfo& ci);
452  GrayCodeCNOT tree_search_over_gate_structures_best_first();
453 
458  N_Qubit_Decomposition_custom perform_optimization(Gates_block* gate_structure_loc);
459 
460  // Bring base class add_finalyzing_layer into scope to avoid hiding
462 
466  void add_finalyzing_layer(Gates_block* gate_structure);
467 
472  void set_unitary(Matrix& Umtx_new);
473 
474  void set_unitary(Matrix_float& Umtx_new);
475 
481  GrayCodeCNOT tabu_search_over_gate_structures();
482 };
483 
484 #endif
std::vector< std::tuple< int, double, std::vector< int >, std::vector< std::pair< int, double > > > > osr_results
Copyright 2021 Budapest Quantum Computing Group.
std::map< GrayCodeCNOT, SearchNode > prefixes
Map from Gray code sequences to their OSR result pairs (rank, cost) for different cuts...
MinCnotBoundSolver osr_bound_solver
Map from CNOT pair (target, control) to the indices of cuts that are affected by this pair...
bool operator<(const SearchNode &other) const
bool operator>(const SearchNode &other) const
CutInfo(std::vector< std::vector< int >> all_cuts, MinCnotBoundSolver osr_bound_solver)
std::set< std::vector< int > > visited
Set of visited states (represented as vectors of integers)
int level_limit_min
The minimal number of adaptive layers used in the decomposition.
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
Header file for Grey code container.
matrix_base< int > possible_target_qbits
List of possible target qubits according to the topology – paired up with possible control qubits...
std::vector< std::vector< int > > q
Queue of states to be processed in the next BFS level.
std::vector< std::vector< int > > cuts_
std::map< GrayCodeCNOT, SearchNode > prefixes
Map of GrayCodeCNOT to OSR (Operator Schmidt Rank) result pairs.
int level_limit
The maximal number of adaptive layers used in the decomposition.
std::vector< std::vector< int > > all_cuts
Vector of all possible qubit cuts, where each cut is represented as a vector of qubit indices...
optimized_parameters_mtx
Definition: vqe_utils.py:38
A base class to determine the decomposition of an N-qubit unitary into a sequence of CNOT and U3 gate...
MinCnotBoundSolver(int num_qubits, const std::vector< std::vector< int >> &cuts, const std::vector< matrix_base< int >> &topology)
SearchNode(GrayCodeCNOT path)
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...
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...
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.
int solve_min_cnots(const std::vector< std::pair< int, double > > &cut_bounds, double &best_kappa, std::vector< int > &best_edge_counts, int max_total=-1) const
Structure containing level information for breadth-first search over gate structures.
std::map< std::vector< int >, GrayCodeCNOT > seq_pairs_of
Map from state vectors to their corresponding Gray code sequences.
int solve_min_cnots(const std::vector< std::pair< int, double > > &cut_bounds, int max_total=-1) const
Double-precision complex matrix (float64).
Definition: matrix.h:38
dictionary config
A class responsible for grouping two-qubit (CNOT,CZ,CH) and one-qubit gates into layers.
Definition: Gates_block.h:44
Single-precision complex matrix (float32).
Definition: matrix_float.h:41
std::vector< GrayCodeCNOT > solutions
Vector of successful Gray-code solutions.
const std::tuple< int, double, std::vector< int >, std::vector< std::pair< int, double > > > & get_best_osr_result() const
bool best_feasible_for_some_composition(int total, std::vector< int > &edge_counts, const std::vector< std::pair< int, double >> &cut_bounds, int pos, int used_sum, double &best_kappa, std::vector< int > &best_edge_counts) const
std::vector< std::vector< int > > cut_to_edges_
Structure containing cut information for operator Schmidt rank (OSR) analysis.
bool feasible_for_some_composition(int total, std::vector< int > &edge_counts, const std::vector< std::pair< int, double > > &cut_bounds, int pos, int used_sum) const
void build_cut_to_edges(const std::vector< matrix_base< int >> &topology)
Class to store data of complex arrays and its properties.
Definition: matrix_real.h:41
bool composition_satisfies(const std::vector< int > &edge_counts, const std::vector< std::pair< int, double > > &cut_bounds) const