QNLP  v1.0
ncu.hpp
Go to the documentation of this file.
1 
9 #ifndef QNLP_NCU
10 #define QNLP_NCU
11 
12 #include <unordered_map>
13 #include <complex>
14 #include <cassert>
15 #include <utility>
16 #include <vector>
17 #include <iostream>
18 
19 #include "GateCache.hpp"
20 #include "mat_ops.hpp"
21 
22 namespace QNLP{
28  template <class SimulatorType>
29  class NCU{
30  private:
31  std::unordered_set<std::string> default_gates {"X", "Y", "Z", "I", "H"};
40  struct OptParamsCX {
41  std::size_t n;
42  std::size_t m;
43  std::size_t l;
44  std::size_t num_ops;
45  };
46 
47  std::unordered_map<std::size_t, std::size_t> op_call_counts_CX ;
48  std::unordered_map<std::size_t, OptParamsCX> opt_op_call_params_CX ;
49 
50  //Goal: replace above hashmaps, as well as sqrtMAtrices etc with the GateCache object, which holds them all.
52 
53  protected:
54  //Take the 2x2 matrix type from the template SimulatorType
55  using Mat2x2Type = decltype(std::declval<SimulatorType>().getGateX());
56 
57  static std::size_t num_gate_ops;
58 
59  public:
64  NCU() {
65  //Optimised building block routines for nCX
66  if(op_call_counts_CX.size() == 0){
67  op_call_counts_CX[3] = 13;
68  op_call_counts_CX[5] = 60;
69  }
70  };
71 
77  NCU(SimulatorType& qSim) : NCU(){
79  }
80 
85  ~NCU(){
86  clearMaps();
87  gate_cache.clearCache();
88  };
89 
95  void initialiseMaps( SimulatorType& qSim, std::size_t num_ctrl_lines){
96  gate_cache.initCache(qSim, num_ctrl_lines);
97  }
98 
104  void addToMaps( SimulatorType& qSim, std::string U_label, const Mat2x2Type& U, std::size_t num_ctrl_lines){
105  gate_cache.addToCache(qSim, U_label, U, num_ctrl_lines);
106  }
107 
114  return gate_cache;
115  }
116 
121  void clearMaps(){
122  gate_cache.clearCache();
123  }
124 
125 
136  void applyNQubitControl(SimulatorType& qSim,
137  const std::vector<std::size_t> ctrlIndices,
138  const std::vector<std::size_t> auxIndices,
139  const unsigned int qTarget,
140  const std::string gateLabel,
141  const Mat2x2Type& U,
142  const std::size_t depth
143  ){
144  //No safety checks; be aware of what is physically possible (qTarget not in control_indices)
145  int local_depth = depth + 1;
146 
147  //Determine the range over which the qubits exist; consider as a count of the control ops, hence +1 since extremeties included
148  std::size_t cOps = ctrlIndices.size();
149 
150  //This can be generalised to find a more optimal set of splitting parameters. Using default example breakdown given in Barenco et al ('95) for now.
151 /* if( (cOps >= 6) && (auxIndices.size() >= 1) && (gateLabel == "X") && (depth == 0) ){
152  auto params = find_optimal_params( ctrlIndices.size() );
153 
154  //Need at least 1 aux qubit to perform decomposition
155  assert( auxIndices.size() > 0);
156 
157  //Gate step 1 setup
158  assert( ctrlIndices.size() >= params.m );
159  std::vector<std::size_t> subMCtrlIndicesNCX(ctrlIndices.begin(), ctrlIndices.begin() + params.m);
160  std::vector<std::size_t> subMAuxIndicesNCX(ctrlIndices.begin() + params.m,ctrlIndices.end());
161  subMAuxIndicesNCX.push_back(qTarget);
162 
163  //Gate step 2 setup
164  std::vector<std::size_t> subLCtrlIndicesNCX(ctrlIndices.begin() + params.m, ctrlIndices.end());
165  subLCtrlIndicesNCX.push_back(auxIndices[0]);
166  std::vector<std::size_t> subLAuxIndicesNCX(ctrlIndices.begin(), ctrlIndices.begin() + params.m);
167 
168  applyNQubitControl(qSim, subMCtrlIndicesNCX, subMAuxIndicesNCX, auxIndices[0], "X", qSim.getGateX(), 0 );
169  applyNQubitControl(qSim, subLCtrlIndicesNCX, subLAuxIndicesNCX, qTarget, "X", qSim.getGateX(), 0 );
170  applyNQubitControl(qSim, subMCtrlIndicesNCX, subMAuxIndicesNCX, auxIndices[0], "X", qSim.getGateX(), 0 );
171  applyNQubitControl(qSim, subLCtrlIndicesNCX, subLAuxIndicesNCX, qTarget, "X", qSim.getGateX(), 0 );
172 
173  }
174 */
175  if( (cOps >= 5) && ( auxIndices.size() >= cOps-2 ) && (gateLabel == "X") && (depth == 0) ){ //161 -> 60 2-qubit gate calls
176  qSim.applyGateCCX( ctrlIndices.back(), *(auxIndices.begin() + ctrlIndices.size() - 3), qTarget);
177 
178  for (std::size_t i = ctrlIndices.size()-2; i >= 2; i--){
179  qSim.applyGateCCX( *(ctrlIndices.begin()+i), *(auxIndices.begin() + (i-2)), *(auxIndices.begin() + (i-1)));
180  }
181  qSim.applyGateCCX( *(ctrlIndices.begin()), *(ctrlIndices.begin()+1), *(auxIndices.begin()) );
182 
183  for (std::size_t i = 2; i <= ctrlIndices.size()-2; i++){
184  qSim.applyGateCCX( *(ctrlIndices.begin()+i), *(auxIndices.begin()+(i-2)), *(auxIndices.begin()+(i-1)));
185  }
186  qSim.applyGateCCX( ctrlIndices.back(), *(auxIndices.begin() + ctrlIndices.size() - 3), qTarget);
187 
188  for (std::size_t i = ctrlIndices.size()-2; i >= 2; i--){
189  qSim.applyGateCCX( *(ctrlIndices.begin()+i), *(auxIndices.begin() + (i-2)), *(auxIndices.begin() + (i-1)));
190  }
191  qSim.applyGateCCX( *(ctrlIndices.begin()), *(ctrlIndices.begin()+1), *(auxIndices.begin()) );
192  for (std::size_t i = 2; i <= ctrlIndices.size()-2; i++){
193  qSim.applyGateCCX( *(ctrlIndices.begin()+i), *(auxIndices.begin()+(i-2)), *(auxIndices.begin()+(i-1)));
194  }
195  }
196 
197  else if(cOps == 3){ //Optimisation for replacing 17 with 13 2-qubit gate calls
198  //Apply the 13 2-qubit gate calls
199  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth+1].first, ctrlIndices[0], qTarget, gateLabel );
200  qSim.applyGateCX( ctrlIndices[0], ctrlIndices[1]);
201 
202  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth+1].second, ctrlIndices[1], qTarget, gateLabel );
203  qSim.applyGateCX( ctrlIndices[0], ctrlIndices[1]);
204 
205  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth+1].first, ctrlIndices[1], qTarget, gateLabel );
206  qSim.applyGateCX( ctrlIndices[1], ctrlIndices[2]);
207 
208  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth+1].second, ctrlIndices[2], qTarget, gateLabel );
209  qSim.applyGateCX( ctrlIndices[0], ctrlIndices[2]);
210 
211  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth+1].first, ctrlIndices[2], qTarget, gateLabel );
212  qSim.applyGateCX( ctrlIndices[1], ctrlIndices[2]);
213 
214  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth+1].second, ctrlIndices[2], qTarget, gateLabel );
215  qSim.applyGateCX( ctrlIndices[0], ctrlIndices[2]);
216 
217  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth+1].first, ctrlIndices[2], qTarget, gateLabel );
218  }
219 
220  else if (cOps >= 2 && cOps !=3){
221  std::vector<std::size_t> subCtrlIndices(ctrlIndices.begin(), ctrlIndices.end()-1);
222 
223  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth].first, ctrlIndices.back(), qTarget, gateLabel );
224 
225  applyNQubitControl(qSim, subCtrlIndices, auxIndices, ctrlIndices.back(), "X", qSim.getGateX(), 0 );
226 
227  qSim.applyGateCU( gate_cache.gateCacheMap[gateLabel][local_depth].second, ctrlIndices.back(), qTarget, gateLabel );
228 
229  applyNQubitControl(qSim, subCtrlIndices, auxIndices, ctrlIndices.back(), "X", qSim.getGateX(), 0 );
230 
231  applyNQubitControl(qSim, subCtrlIndices, auxIndices, qTarget, gateLabel, gate_cache.gateCacheMap[gateLabel][local_depth+1].first, local_depth );
232  }
233 
234  //If the number of control qubits is less than 2, assume we have decomposed sufficiently
235  else{
236  qSim.applyGateCU(gate_cache.gateCacheMap[gateLabel][depth].first, ctrlIndices[0], qTarget, gateLabel); //The first decomposed matrix value is used here
237  }
238  }
239 
247  std::size_t get_op_calls(std::size_t num_ctrl_lines){
248  std::size_t num_ops = 0;
249  auto it = op_call_counts_CX.find(num_ctrl_lines);
250  if ( it != op_call_counts_CX.end() ){
251  std::cout << "get_op_calls CTRL=" << num_ctrl_lines << " OPS=" << it->second << std::endl;
252 
253  return it->second;
254  }
255  else if (num_ctrl_lines >= 2){
256  num_ops += (2 + 3*get_op_calls(num_ctrl_lines-1));
257  }
258  else{
259  num_ops += 1;
260  }
261  op_call_counts_CX[num_ctrl_lines] = num_ops;
262  std::cout << "get_op_calls CTRL=" << num_ctrl_lines << " OPS=" << num_ops << std::endl;
263 
264  return num_ops;
265  }
266 
275  inline std::size_t get_ops_for_params(std::size_t l, std::size_t m){
276  return 2*(get_op_calls(m) + get_op_calls(l));
277  }
278 
286  OptParamsCX find_optimal_params(std::size_t num_ctrl_lines){
287 
288  //If entry exists, return it
289  auto it = opt_op_call_params_CX.find(num_ctrl_lines);
290  if ( it != opt_op_call_params_CX.end() ){
291  std::cout << "FOUND OPTIMAL PARAMS SEARCH FOR CTRL=" << num_ctrl_lines << " M=" << it->second.m << " L=" << it->second.l << " OPS=" << it->second.num_ops << std::endl;
292 
293  return it->second;
294  }
295 
296  //Determine number of comparisons to make.
297  std::size_t num_comp = static_cast<std::size_t>(std::floor((float)(num_ctrl_lines)/2.0)) +1;
298 
299  OptParamsCX optimal_params;
300  std::size_t tmp_num_ops;
301 
302  for(std::size_t _m = 2; _m <= num_comp; ++_m){
303  std::size_t _l = (num_ctrl_lines - _m + 1);
304  tmp_num_ops = get_ops_for_params(_l, _m);
305  if (_m == 2){
306  optimal_params = OptParamsCX{num_ctrl_lines, _m, _l, tmp_num_ops};
307  }
308  else if ( tmp_num_ops < optimal_params.num_ops){
309  optimal_params = OptParamsCX{num_ctrl_lines, _m, _l, tmp_num_ops};
310  }
311  std::cout << "OPTIMAL PARAMS SEARCH FOR CTRL=" << num_ctrl_lines << " M=" << _m << " L=" << _l << " OPS=" << tmp_num_ops << std::endl;
312  }
313  opt_op_call_params_CX[num_ctrl_lines] = optimal_params;
314  op_call_counts_CX[num_ctrl_lines] = optimal_params.num_ops;
315  std::cout << "OPTIMAL PARAMS FOR CTRL=" << num_ctrl_lines << " M=" << optimal_params.m << " L=" << optimal_params.l << " OPS=" << optimal_params.num_ops << std::endl;
316  return optimal_params;
317  }
318  };
319 
320 };
321 #endif
GateCache< SimulatorType > gate_cache
Definition: ncu.hpp:51
OptParamsCX find_optimal_params(std::size_t num_ctrl_lines)
Returns the number of 2-qubit operations an optimised decomposition will make for nCX....
Definition: ncu.hpp:286
std::unordered_set< std::string > default_gates
Definition: ncu.hpp:31
For the 5+ controlled NCX decomposition routines defined within https://arxiv.org/pdf/quant-ph/950301...
Definition: ncu.hpp:40
static std::size_t num_gate_ops
Definition: ncu.hpp:57
NCU(SimulatorType &qSim)
Construct a new NCU object.
Definition: ncu.hpp:77
Class to cache intermediate matrix values used within other parts of the computation....
Definition: GateCache.hpp:95
std::size_t num_ops
Definition: ncu.hpp:44
Templated methods to manipulate small matrices.
std::unordered_map< std::size_t, std::size_t > op_call_counts_CX
Definition: ncu.hpp:47
std::size_t n
Definition: ncu.hpp:41
Class definition for applying n-qubit controlled unitary operations.
Definition: ncu.hpp:29
void addToMaps(SimulatorType &qSim, std::string U_label, const Mat2x2Type &U, std::size_t num_ctrl_lines)
Add the given unitary matrix to the maps up to the required depth.
Definition: ncu.hpp:104
std::size_t l
Definition: ncu.hpp:43
~NCU()
Destroy the NCU object.
Definition: ncu.hpp:85
void applyNQubitControl(SimulatorType &qSim, const std::vector< std::size_t > ctrlIndices, const std::vector< std::size_t > auxIndices, const unsigned int qTarget, const std::string gateLabel, const Mat2x2Type &U, const std::size_t depth)
Decompose n-qubit controlled op into 1 and 2 qubit gates. Control indices can be in any specified loc...
Definition: ncu.hpp:136
std::unordered_map< std::size_t, OptParamsCX > opt_op_call_params_CX
Definition: ncu.hpp:48
std::size_t m
Definition: ncu.hpp:42
GateCache< SimulatorType > & getGateCache()
Get the Map of cached gates. Keys are strings, and values are vectors of paired (gate,...
Definition: ncu.hpp:113
void clearMaps()
Clears the maps of stored sqrt matrices.
Definition: ncu.hpp:121
void initialiseMaps(SimulatorType &qSim, std::size_t num_ctrl_lines)
Add the PauliX and the given unitary U to the maps.
Definition: ncu.hpp:95
std::size_t get_op_calls(std::size_t num_ctrl_lines)
Returns the number of 2-qubit operations a non optimised decomposition will make. Cache intermediate ...
Definition: ncu.hpp:247
NCU()
Construct a new NCU object.
Definition: ncu.hpp:64
std::size_t get_ops_for_params(std::size_t l, std::size_t m)
Helper method to get optimised number of 2-gate ops for given decomposition params.
Definition: ncu.hpp:275
decltype(std::declval< SimulatorType >().getGateX()) Mat2x2Type
Definition: ncu.hpp:55