QCOR
quantum_kernel.hpp
1 #pragma once
2 #include <optional>
3 #include <stack>
4 
5 #include "gradient_function.hpp"
6 #include "qcor_jit.hpp"
7 #include "qcor_observable.hpp"
8 #include "qcor_utils.hpp"
9 #include "qrt.hpp"
10 
11 namespace qcor {
12 enum class QrtType { NISQ, FTQC };
13 
14 // Forward declare
15 template <typename... Args>
17 
18 namespace internal {
19 // KernelSignature is the base of all kernel-like objects
20 // and we use it to implement kernel modifiers & utilities.
21 // i.e., anything that is KernelSignature-constructible can use these methods.
22 template <typename... Args>
23 void apply_control(std::shared_ptr<CompositeInstruction> parent_kernel,
24  const std::vector<qubit> &ctrl_qbits,
25  KernelSignature<Args...> &kernelCallable, Args... args);
26 
27 template <typename... Args>
28 void apply_adjoint(std::shared_ptr<CompositeInstruction> parent_kernel,
29  KernelSignature<Args...> &kernelCallable, Args... args);
30 
31 template <typename... Args>
32 double observe(Operator &obs, KernelSignature<Args...> &kernelCallable,
33  Args... args);
34 
35 template <typename... Args>
36 UnitaryMatrix as_unitary_matrix(KernelSignature<Args...> &kernelCallable,
37  Args... args);
38 template <typename... Args>
39 std::string openqasm(KernelSignature<Args...> &kernelCallable, Args... args);
40 
41 template <typename... Args>
42 void print_kernel(KernelSignature<Args...> &kernelCallable, std::ostream &os,
43  Args... args);
44 
45 template <typename... Args>
46 std::size_t n_instructions(KernelSignature<Args...> &kernelCallable,
47  Args... args);
48 } // namespace internal
49 
50 // The QuantumKernel represents the super-class of all qcor
51 // quantum kernel functors. Subclasses of this are auto-generated
52 // via the Clang Syntax Handler capability. Derived classes
53 // provide a destructor implementation that builds up and
54 // submits quantum instructions to the specified backend. This enables
55 // functor-like capability whereby programmers instantiate temporary
56 // instances of this via the constructor call, and the destructor is
57 // immediately called. More advanced usage is of course possible for
58 // qcor developers.
59 //
60 // This class works by taking the Derived type (CRTP) and the kernel function
61 // arguments as template parameters. The Derived type is therefore available for
62 // instantiation within provided static methods on QuantumKernel. The Args...
63 // are stored in a member tuple, and are available for use when evaluating the
64 // kernel. Importantly, QuantumKernel provides static adjoint and ctrl methods
65 // for auto-generating those circuits.
66 //
67 // The Syntax Handler will take kernels like this
68 // __qpu__ void foo(qreg q) { H(q[0]); }
69 // and create a derived type of QuantumKernel like this
70 // class foo : public qcor::QuantumKernel<class foo, qreg> {...};
71 // with an appropriate implementation of constructors and destructors.
72 // Users can then call for adjoint/ctrl methods like this
73 // foo::adjoint(q); foo::ctrl(1, q);
74 template <typename Derived, typename... Args>
76  protected:
77  // Tuple holder for variadic kernel arguments
78  std::tuple<Args...> args_tuple;
79 
80  // Parent kernel - null if this is the top-level kernel
81  // not null if this is a nested kernel call
82  std::shared_ptr<qcor::CompositeInstruction> parent_kernel;
83 
84  // Default, submit this kernel, if parent is given
85  // turn this to false
86  bool is_callable = true;
87 
88  // Turn off destructor execution, useful for
89  // qcor developers, not to be used by clients / programmers
90  bool disable_destructor = false;
91 
92  public:
93  // Flag to indicate we only want to
94  // run the pass manager and not execute
95  bool optimize_only = false;
96  QrtType runtime_env = QrtType::NISQ;
97  // Default constructor, takes quantum kernel function arguments
98  QuantumKernel(Args... args) : args_tuple(std::forward_as_tuple(args...)) {
99  runtime_env = (__qrt_env == "ftqc") ? QrtType::FTQC : QrtType::NISQ;
100  }
101 
102  // Internal constructor, provide parent kernel, this
103  // kernel now represents a nested kernel call and
104  // appends to the parent kernel
105  QuantumKernel(std::shared_ptr<qcor::CompositeInstruction> _parent_kernel,
106  Args... args)
107  : args_tuple(std::forward_as_tuple(args...)),
108  parent_kernel(_parent_kernel),
109  is_callable(false) {
110  runtime_env = (__qrt_env == "ftqc") ? QrtType::FTQC : QrtType::NISQ;
111  }
112 
113  // Static method for printing this kernel as a flat qasm string
114  static void print_kernel(std::ostream &os, Args... args) {
115  Derived derived(args...);
116  KernelSignature<Args...> callable(derived);
117  return internal::print_kernel<Args...>(callable, os, args...);
118  }
119 
120  static void print_kernel(Args... args) { print_kernel(std::cout, args...); }
121 
122  // Static method to query how many instructions are in this kernel
123  static std::size_t n_instructions(Args... args) {
124  Derived derived(args...);
125  KernelSignature<Args...> callable(derived);
126  return internal::n_instructions<Args...>(callable, args...);
127  }
128 
129  // Create the Adjoint of this quantum kernel
130  static void adjoint(std::shared_ptr<CompositeInstruction> parent_kernel,
131  Args... args) {
132  Derived derived(args...);
133  KernelSignature<Args...> callable(derived);
134  return internal::apply_adjoint<Args...>(parent_kernel, callable, args...);
135  }
136 
137  // Create the controlled version of this quantum kernel
138  static void ctrl(std::shared_ptr<CompositeInstruction> parent_kernel,
139  const std::vector<int> &ctrlIdx, Args... args) {
140  std::vector<qubit> ctrl_qubit_vec;
141  for (int i = 0; i < ctrlIdx.size(); i++) {
142  ctrl_qubit_vec.push_back({"q", static_cast<size_t>(ctrlIdx[i]), nullptr});
143  }
144  ctrl(parent_kernel, ctrl_qubit_vec, args...);
145  }
146 
147  // Single-qubit overload
148  static void ctrl(std::shared_ptr<CompositeInstruction> parent_kernel,
149  int ctrlIdx, Args... args) {
150  ctrl(parent_kernel, std::vector<int>{ctrlIdx}, args...);
151  }
152 
153  static void ctrl(std::shared_ptr<CompositeInstruction> parent_kernel,
154  qreg ctrl_qbits, Args... args) {
155  std::vector<qubit> ctrl_qubit_vec;
156  for (int i = 0; i < ctrl_qbits.size(); i++)
157  ctrl_qubit_vec.push_back(ctrl_qbits[i]);
158 
159  ctrl(parent_kernel, ctrl_qubit_vec, args...);
160  }
161 
162  static void ctrl(std::shared_ptr<CompositeInstruction> parent_kernel,
163  const std::vector<qubit> &ctrl_qbits, Args... args) {
164  // instantiate and don't let it call the destructor
165  Derived derived(args...);
166  KernelSignature<Args...> callable(derived);
167  internal::apply_control<Args...>(parent_kernel, ctrl_qbits, callable,
168  args...);
169  }
170 
171  // Create the controlled version of this quantum kernel
172  static void ctrl(std::shared_ptr<CompositeInstruction> parent_kernel,
173  qubit ctrl_qbit, Args... args) {
174  ctrl(parent_kernel, std::vector<qubit>{ctrl_qbit}, args...);
175  }
176 
177  static UnitaryMatrix as_unitary_matrix(Args... args) {
178  Derived derived(args...);
179  KernelSignature<Args...> callable(derived);
180  return internal::as_unitary_matrix<Args...>(callable, args...);
181  }
182 
183  static double observe(Operator &obs, Args... args) {
184  Derived derived(args...);
185  KernelSignature<Args...> callable(derived);
186  return internal::observe<Args...>(obs, callable, args...);
187  }
188 
189  static double observe(std::shared_ptr<Operator> obs, Args... args) {
190  return observe(*obs, args...);
191  }
192 
193  // Simple autograd support for kernel with simple type: double or
194  // vector<double>. Other signatures must provide a translator...
195  static double autograd(Operator &obs, std::vector<double> &dx, qreg q,
196  double x) {
197  std::function<std::shared_ptr<CompositeInstruction>(std::vector<double>)>
198  kernel_eval = [q](std::vector<double> x) {
199  auto tempKernel =
200  qcor::__internal__::create_composite("__temp__autograd__");
201  Derived derived(q, x[0]);
202  derived.disable_destructor = true;
203  derived(q, x[0]);
204  tempKernel->addInstructions(derived.parent_kernel->getInstructions());
205  return tempKernel;
206  };
207 
208  auto gradiend_method = qcor::__internal__::get_gradient_method(
209  qcor::__internal__::DEFAULT_GRADIENT_METHOD, kernel_eval, obs);
210  const double cost_val = observe(obs, q, x);
211  dx = (*gradiend_method)({x}, cost_val);
212  return cost_val;
213  }
214 
215  static double autograd(Operator &obs, std::vector<double> &dx, qreg q,
216  std::vector<double> x) {
217  std::function<std::shared_ptr<CompositeInstruction>(std::vector<double>)>
218  kernel_eval = [q](std::vector<double> x) {
219  auto tempKernel =
220  qcor::__internal__::create_composite("__temp__autograd__");
221  Derived derived(q, x);
222  derived.disable_destructor = true;
223  derived(q, x);
224  tempKernel->addInstructions(derived.parent_kernel->getInstructions());
225  return tempKernel;
226  };
227 
228  auto gradiend_method = qcor::__internal__::get_gradient_method(
229  qcor::__internal__::DEFAULT_GRADIENT_METHOD, kernel_eval, obs);
230  const double cost_val = observe(obs, q, x);
231  dx = (*gradiend_method)(x, cost_val);
232  return cost_val;
233  }
234 
235  static double autograd(Operator &obs, std::vector<double> &dx,
236  std::vector<double> x,
237  ArgsTranslator<Args...> args_translator) {
238  std::function<std::shared_ptr<CompositeInstruction>(std::vector<double>)>
239  kernel_eval = [&](std::vector<double> x_vec) {
240  auto eval_lambda = [&](Args... args) {
241  auto tempKernel =
242  qcor::__internal__::create_composite("__temp__autograd__");
243  Derived derived(args...);
244  derived.disable_destructor = true;
245  derived(args...);
246  tempKernel->addInstructions(
247  derived.parent_kernel->getInstructions());
248  return tempKernel;
249  };
250  auto args_tuple = args_translator(x_vec);
251  return std::apply(eval_lambda, args_tuple);
252  };
253 
254  auto gradiend_method = qcor::__internal__::get_gradient_method(
255  qcor::__internal__::DEFAULT_GRADIENT_METHOD, kernel_eval, obs);
256 
257  auto kernel_observe = [&](Args... args) { return observe(obs, args...); };
258 
259  auto args_tuple = args_translator(x);
260  const double cost_val = std::apply(kernel_observe, args_tuple);
261  dx = (*gradiend_method)(x, cost_val);
262  return cost_val;
263  }
264 
265  static std::string openqasm(Args... args) {
266  Derived derived(args...);
267  KernelSignature<Args...> callable(derived);
268  return internal::openqasm<Args...>(callable, args...);
269  }
270 
271  virtual ~QuantumKernel() {}
272 
273  template <typename... ArgTypes>
274  friend class KernelSignature;
275 };
276 
277 // We use the following to enable ctrl operations on our single
278 // qubit gates, X::ctrl(), Z::ctrl(), H::ctrl(), etc....
279 template <typename Derived>
281 
282 #define ONE_QUBIT_KERNEL_CTRL_ENABLER(CLASSNAME, QRTNAME) \
283  class CLASSNAME : public OneQubitKernel<class CLASSNAME> { \
284  public: \
285  CLASSNAME(qubit q) : OneQubitKernel<CLASSNAME>(q) {} \
286  CLASSNAME(std::shared_ptr<qcor::CompositeInstruction> _parent_kernel, \
287  qubit q) \
288  : OneQubitKernel<CLASSNAME>(_parent_kernel, q) { \
289  throw std::runtime_error("you cannot call this."); \
290  } \
291  void operator()(qubit q) { \
292  parent_kernel = qcor::__internal__::create_composite( \
293  "__tmp_one_qubit_ctrl_enabler"); \
294  quantum::set_current_program(parent_kernel); \
295  if (runtime_env == QrtType::FTQC) { \
296  quantum::set_current_buffer(q.results()); \
297  } \
298  ::quantum::QRTNAME(q); \
299  return; \
300  } \
301  virtual ~CLASSNAME() {} \
302  };
303 
304 ONE_QUBIT_KERNEL_CTRL_ENABLER(X, x)
305 ONE_QUBIT_KERNEL_CTRL_ENABLER(Y, y)
306 ONE_QUBIT_KERNEL_CTRL_ENABLER(Z, z)
307 ONE_QUBIT_KERNEL_CTRL_ENABLER(H, h)
308 ONE_QUBIT_KERNEL_CTRL_ENABLER(T, t)
309 ONE_QUBIT_KERNEL_CTRL_ENABLER(Tdg, tdg)
310 ONE_QUBIT_KERNEL_CTRL_ENABLER(S, s)
311 ONE_QUBIT_KERNEL_CTRL_ENABLER(Sdg, sdg)
312 
313 // The following is a first pass at enabling qcor
314 // quantum lambdas. The goal is to mimic lambda functionality
315 // via our QJIT infrastructure. The lambda class takes
316 // as input a lambda of desired kernel signature calling
317 // a specific macro which expands to return the function body
318 // expression as a string, which we use with QJIT jit_compile.
319 // The lambda class is templated on the types of any capture variables
320 // the programmer would like to specify, and takes a second constructor
321 // argument indicating the variable names of all kernel arguments and
322 // capture variables. Finally, all capture variables must be passed to the
323 // trailing variadic argument for the lambda class constructor. Once
324 // instantiated lambda invocation looks just like kernel invocation.
325 
326 template <typename... CaptureArgs>
327 class _qpu_lambda {
328  private:
329  // Private inner class for getting the type
330  // of a capture variable as a string at runtime
331  class TupleToTypeArgString {
332  protected:
333  std::string &tmp;
334  std::vector<std::string> var_names;
335  int counter = 0;
336 
337  template <class T>
338  std::string type_name() {
339  typedef typename std::remove_reference<T>::type TR;
340  std::unique_ptr<char, void (*)(void *)> own(
341  abi::__cxa_demangle(typeid(TR).name(), nullptr, nullptr, nullptr),
342  std::free);
343  std::string r = own != nullptr ? own.get() : typeid(TR).name();
344  return r;
345  }
346 
347  public:
348  TupleToTypeArgString(std::string &t) : tmp(t) {}
349  TupleToTypeArgString(std::string &t, std::vector<std::string> &_var_names)
350  : tmp(t), var_names(_var_names) {}
351  template <typename T>
352  void operator()(T &t) {
353  tmp += type_name<decltype(t)>() + "& " +
354  (var_names.empty() ? "arg_" + std::to_string(counter)
355  : var_names[counter]) +
356  ",";
357  counter++;
358  }
359  };
360 
361  // Kernel lambda source string, has arg structure and body
362  std::string &src_str;
363 
364  // Capture variable names
365  std::string &capture_var_names;
366 
367  // By-ref capture variables, stored in tuple
368  std::tuple<CaptureArgs &...> capture_vars;
369 
370  // Optional capture *by-value* variables:
371  // We don't want to make unnecessary copies of capture variables
372  // unless explicitly requested ("[=]").
373  // Also, some types may not be copy-constructable...
374  // Notes:
375  // (1) we must copy at the lambda declaration point (i.e. _qpu_lambda
376  // constructor)
377  // (2) our JIT code chain is constructed using the by-reference convention,
378  // just need to handle by-value (copy) at the top-level (i.e., in this tuple
379  // storage)
380  std::optional<std::tuple<CaptureArgs...>> optional_copy_capture_vars;
381 
382  // Quantum Just-in-Time Compiler :)
383  QJIT qjit;
384 
385  public:
386  // Variational information, i.e. is this lambda compatible with VQE
387  // e.g. single double or single vector double input.
388  enum class Variational_Arg_Type { Double, Vec_Double, None };
389  Variational_Arg_Type var_type = Variational_Arg_Type::None;
390 
391  // Constructor, capture vars should be deduced without
392  // specifying them since we're using C++17
393  _qpu_lambda(std::string &&ff, std::string &&_capture_var_names,
394  CaptureArgs &..._capture_vars)
395  : src_str(ff),
396  capture_var_names(_capture_var_names),
397  capture_vars(std::forward_as_tuple(_capture_vars...)) {
398  // Get the original args list
399  auto first = src_str.find_first_of("(");
400  auto last = src_str.find_first_of(")");
401  auto tt = src_str.substr(first, last - first + 1);
402  // Parse the argument list
403  const auto arg_type_and_names = [](const std::string &arg_string_decl)
404  -> std::vector<std::pair<std::string, std::string>> {
405  // std::cout << "HOWDY:" << arg_string_decl << "\n";
406  std::vector<std::pair<std::string, std::string>> result;
407  const auto args_string =
408  arg_string_decl.substr(1, arg_string_decl.size() - 2);
409  std::stack<char> grouping_chars;
410  std::string type_name;
411  std::string var_name;
412  std::string temp;
413  // std::cout << args_string << "\n";
414  for (int i = 0; i < args_string.size(); ++i) {
415  if (isspace(args_string[i]) && grouping_chars.empty()) {
416  type_name = temp;
417  temp.clear();
418  } else if (args_string[i] == ',') {
419  var_name = temp;
420  if (var_name[0] == '&') {
421  type_name += "&";
422  var_name = var_name.substr(1);
423  }
424  result.emplace_back(std::make_pair(type_name, var_name));
425  type_name.clear();
426  var_name.clear();
427  temp.clear();
428  } else {
429  temp.push_back(args_string[i]);
430  }
431 
432  if (args_string[i] == '<') {
433  grouping_chars.push(args_string[i]);
434  }
435  if (args_string[i] == '>') {
436  // assert(grouping_chars.top() == '<');
437  grouping_chars.pop();
438  }
439  }
440 
441  // Last one:
442  var_name = temp;
443  if (var_name[0] == '&') {
444  type_name += "&";
445  var_name = var_name.substr(1);
446  }
447  result.emplace_back(std::make_pair(type_name, var_name));
448  return result;
449  }(tt);
450 
451  // Determine if this lambda has a VQE-compatible type:
452  // QReg then variational params.
453  if (arg_type_and_names.size() == 2) {
454  const auto trim_space = [](std::string &stripString) {
455  while (!stripString.empty() && std::isspace(*stripString.begin())) {
456  stripString.erase(stripString.begin());
457  }
458 
459  while (!stripString.empty() && std::isspace(*stripString.rbegin())) {
460  stripString.erase(stripString.length() - 1);
461  }
462  };
463 
464  auto type_name = arg_type_and_names[1].first;
465  trim_space(type_name);
466  // Use a relax search to handle using namespace std...
467  // FIXME: this is quite hacky.
468  if (type_name.find("vector<double>") != std::string::npos) {
469  var_type = Variational_Arg_Type::Vec_Double;
470  } else if (type_name == "double") {
471  var_type = Variational_Arg_Type::Double;
472  }
473  }
474 
475  // Map simple type to its reference type so that the
476  // we can use consistent type-forwarding
477  // when casting the JIT raw function pointer.
478  // Currently, looks like only these simple types are having problem
479  // with perfect type forwarding.
480  // i.e. by-value arguments of these types are incompatible with a by-ref
481  // casted function.
482  static const std::unordered_map<std::string, std::string>
483  FORWARD_TYPE_CONVERSION_MAP{{"int", "int&"}, {"double", "double&"}};
484  std::vector<std::pair<std::string, std::string>> forward_types;
485  // Replicate by-value by create copies and restore the variables.
486  std::vector<std::string> byval_casted_arg_names;
487  for (const auto &[type, name] : arg_type_and_names) {
488  // std::cout << type << " --> " << name << "\n";
489  if (FORWARD_TYPE_CONVERSION_MAP.find(type) !=
490  FORWARD_TYPE_CONVERSION_MAP.end()) {
491  auto iter = FORWARD_TYPE_CONVERSION_MAP.find(type);
492  forward_types.emplace_back(std::make_pair(iter->second, name));
493  byval_casted_arg_names.emplace_back(name);
494  } else {
495  forward_types.emplace_back(std::make_pair(type, name));
496  }
497  }
498 
499  // std::cout << "After\n";
500  // Construct the new arg signature clause:
501  std::string arg_clause_new;
502  arg_clause_new.push_back('(');
503  for (const auto &[type, name] : forward_types) {
504  arg_clause_new.append(type);
505  arg_clause_new.push_back(' ');
506  arg_clause_new.append(name);
507  arg_clause_new.push_back(',');
508  // std::cout << type << " --> " << name << "\n";
509  }
510  arg_clause_new.pop_back();
511 
512  // Get the capture type:
513  // By default "[]", pass by reference.
514  // [=]: pass by value
515  // [&]: pass by reference (same as default)
516  const auto first_square_bracket = src_str.find_first_of("[");
517  const auto last_square_bracket = src_str.find_first_of("]");
518  const auto capture_type = src_str.substr(
519  first_square_bracket, last_square_bracket - first_square_bracket + 1);
520  if (!capture_type.empty() && capture_type == "[=]") {
521  // We must check this at compile-time to prevent the compiler from
522  // *attempting* to compile this code path even when by-val capture is not
523  // in use. The common scenario is a qpu_lambda captures other qpu_lambda.
524  // Copying of qpu_lambda by value is prohibitied.
525  // We'll report a runtime error for this case.
526  if constexpr (std::conjunction_v<
527  std::is_copy_assignable<CaptureArgs>...>) {
528  // Store capture vars (by-value)
529  optional_copy_capture_vars = std::forward_as_tuple(_capture_vars...);
530  } else {
531  error(
532  "Capture variable type is non-copyable. Cannot use capture by "
533  "value.");
534  }
535  }
536 
537  // Need to append capture vars to this arg signature
538  std::string capture_preamble = "";
539  const auto replaceVarName = [](std::string &str, const std::string &from,
540  const std::string &to) {
541  size_t start_pos = str.find(from);
542  if (start_pos != std::string::npos) {
543  str.replace(start_pos, from.length(), to);
544  }
545  };
546  if (!capture_var_names.empty()) {
547  std::string args_string = "";
548  TupleToTypeArgString co(args_string);
549  __internal__::tuple_for_each(capture_vars, co);
550  args_string = "," + args_string.substr(0, args_string.length() - 1);
551 
552  // Replace the generic argument names (tuple foreach)
553  // with the actual capture var name.
554  // We need to do this so that the SyntaxHandler can properly detect if
555  // a capture var is a Kernel-like ==> add the list of in-flight kernels
556  // and add parent_kernel to the invocation.
557  for (auto [i, capture_name] :
558  qcor::enumerate(qcor::split(capture_var_names, ','))) {
559  const auto old_name = "arg_" + std::to_string(i);
560  replaceVarName(args_string, old_name, capture_name);
561  }
562 
563  tt.insert(last - capture_type.size(), args_string);
564  arg_clause_new.append(args_string);
565  }
566 
567  // Extract the function body
568  first = src_str.find_first_of("{");
569  last = src_str.find_last_of("}");
570  auto rr = src_str.substr(first, last - first + 1);
571  arg_clause_new.push_back(')');
572  // std::cout << "New signature: " << arg_clause_new << "\n";
573  // Reconstruct with new args signature and
574  // existing function body
575  std::stringstream ss;
576  ss << "__qpu__ void foo" << arg_clause_new << rr;
577 
578  // Get as a string, and insert capture
579  // preamble if necessary
580  auto jit_src = ss.str();
581  first = jit_src.find_first_of("{");
582  if (!capture_var_names.empty()) jit_src.insert(first + 1, capture_preamble);
583 
584  if (!byval_casted_arg_names.empty()) {
585  std::stringstream cache_string, restore_string;
586  for (const auto &var : byval_casted_arg_names) {
587  cache_string << "auto __" << var << "__cached__ = " << var << ";\n";
588  restore_string << var << " = __" << var << "__cached__;\n";
589  }
590  const auto begin = jit_src.find_first_of("{");
591  jit_src.insert(begin + 1, cache_string.str());
592  const auto end = jit_src.find_last_of("}");
593  jit_src.insert(end, restore_string.str());
594  }
595 
596  std::cout << "JITSRC:\n" << jit_src << "\n";
597  // JIT Compile, storing the function pointers
598  qjit.jit_compile(jit_src);
599  }
600 
601  template <typename... FunctionArgs>
602  void eval_with_parent(std::shared_ptr<CompositeInstruction> parent,
603  FunctionArgs &&...args) {
604  this->operator()(parent, std::forward<FunctionArgs>(args)...);
605  }
606 
607  template <typename... FunctionArgs>
608  void operator()(std::shared_ptr<CompositeInstruction> parent,
609  FunctionArgs &&...args) {
610  // Map the function args to a tuple
611  auto kernel_args_tuple = std::forward_as_tuple(args...);
612 
613  if (!optional_copy_capture_vars.has_value()) {
614  // By-ref:
615  // Merge the function args and the capture vars and execute
616  auto final_args_tuple = std::tuple_cat(kernel_args_tuple, capture_vars);
617  std::apply(
618  [&](auto &&...args) {
619  qjit.invoke_with_parent_forwarding("foo", parent, args...);
620  },
621  final_args_tuple);
622  } else if constexpr (std::conjunction_v<
623  std::is_copy_assignable<CaptureArgs>...>) {
624  // constexpr compile-time check to prevent compiler from looking at this
625  // code path if the capture variable is non-copyable, e.g. qpu_lambda.
626  // By-value:
627  auto final_args_tuple =
628  std::tuple_cat(kernel_args_tuple, optional_copy_capture_vars.value());
629  std::apply(
630  [&](auto &&...args) {
631  qjit.invoke_with_parent_forwarding("foo", parent, args...);
632  },
633  final_args_tuple);
634  }
635  }
636 
637  template <typename... FunctionArgs>
638  void operator()(FunctionArgs &&...args) {
639  // Map the function args to a tuple
640  auto kernel_args_tuple = std::forward_as_tuple(args...);
641  if (!optional_copy_capture_vars.has_value()) {
642  // By-ref
643  // Merge the function args and the capture vars and execute
644  auto final_args_tuple = std::tuple_cat(kernel_args_tuple, capture_vars);
645  std::apply(
646  [&](auto &&...args) { qjit.invoke_forwarding("foo", args...); },
647  final_args_tuple);
648  } else if constexpr (std::conjunction_v<
649  std::is_copy_assignable<CaptureArgs>...>) {
650  // By-value
651  auto final_args_tuple =
652  std::tuple_cat(kernel_args_tuple, optional_copy_capture_vars.value());
653  std::apply(
654  [&](auto &&...args) { qjit.invoke_forwarding("foo", args...); },
655  final_args_tuple);
656  }
657  }
658 
659  template <typename... FunctionArgs>
660  double observe(std::shared_ptr<Operator> obs, FunctionArgs... args) {
661  return observe(*obs.get(), args...);
662  }
663 
664  template <typename... FunctionArgs>
665  double observe(Operator &obs, FunctionArgs... args) {
666  KernelSignature<FunctionArgs...> callable(*this);
667  return internal::observe<FunctionArgs...>(obs, callable, args...);
668  }
669 
670  template <typename... FunctionArgs>
671  void ctrl(std::shared_ptr<CompositeInstruction> ir,
672  const std::vector<qubit> &ctrl_qbits, FunctionArgs... args) {
673  KernelSignature<FunctionArgs...> callable(*this);
674  internal::apply_control<FunctionArgs...>(ir, ctrl_qbits, callable, args...);
675  }
676 
677  template <typename... FunctionArgs>
678  void ctrl(std::shared_ptr<CompositeInstruction> ir,
679  const std::vector<int> &ctrl_idxs, FunctionArgs... args) {
680  std::vector<qubit> ctrl_qubit_vec;
681  for (int i = 0; i < ctrl_idxs.size(); i++) {
682  ctrl_qubit_vec.push_back(
683  {"q", static_cast<size_t>(ctrl_idxs[i]), nullptr});
684  }
685  ctrl(ir, ctrl_qubit_vec, args...);
686  }
687 
688  template <typename... FunctionArgs>
689  void ctrl(std::shared_ptr<CompositeInstruction> ir, int ctrl_qbit,
690  FunctionArgs... args) {
691  ctrl(ir, std::vector<int>{ctrl_qbit}, args...);
692  }
693 
694  template <typename... FunctionArgs>
695  void ctrl(std::shared_ptr<CompositeInstruction> ir, qubit ctrl_qbit,
696  FunctionArgs... args) {
697  ctrl(ir, std::vector<qubit>{ctrl_qbit}, args...);
698  }
699 
700  template <typename... FunctionArgs>
701  void ctrl(std::shared_ptr<CompositeInstruction> ir, qreg ctrl_qbits,
702  FunctionArgs... args) {
703  std::vector<qubit> ctrl_qubit_vec;
704  for (int i = 0; i < ctrl_qbits.size(); i++) {
705  ctrl_qubit_vec.push_back(ctrl_qbits[i]);
706  }
707  ctrl(ir, ctrl_qubit_vec, args...);
708  }
709 
710  template <typename... FunctionArgs>
711  void adjoint(std::shared_ptr<CompositeInstruction> parent_kernel,
712  FunctionArgs... args) {
713  KernelSignature<FunctionArgs...> callable(*this);
714  return internal::apply_adjoint<FunctionArgs...>(parent_kernel, callable,
715  args...);
716  }
717 
718  template <typename... FunctionArgs>
719  void print_kernel(std::ostream &os, FunctionArgs... args) {
720  KernelSignature<FunctionArgs...> callable(*this);
721  return internal::print_kernel<FunctionArgs...>(callable, os, args...);
722  }
723 
724  template <typename... FunctionArgs>
725  void print_kernel(FunctionArgs... args) {
726  print_kernel(std::cout, args...);
727  }
728 
729  template <typename... FunctionArgs>
730  std::size_t n_instructions(FunctionArgs... args) {
731  KernelSignature<FunctionArgs...> callable(*this);
732  return internal::n_instructions<FunctionArgs...>(callable, args...);
733  }
734 
735  template <typename... FunctionArgs>
736  UnitaryMatrix as_unitary_matrix(FunctionArgs... args) {
737  KernelSignature<FunctionArgs...> callable(*this);
738  return internal::as_unitary_matrix<FunctionArgs...>(callable, args...);
739  }
740 
741  template <typename... FunctionArgs>
742  std::string openqasm(FunctionArgs... args) {
743  KernelSignature<FunctionArgs...> callable(*this);
744  return internal::openqasm<FunctionArgs...>(callable, args...);
745  }
746 };
747 
748 #define qpu_lambda(EXPR, ...) _qpu_lambda(#EXPR, #__VA_ARGS__, ##__VA_ARGS__)
749 
750 template <typename... Args>
751 using callable_function_ptr = void (*)(std::shared_ptr<CompositeInstruction>,
752  Args...);
753 
754 template <typename... Args>
755 class KernelSignature {
756  private:
757  callable_function_ptr<Args...> *readOnly = 0;
758  callable_function_ptr<Args...> &function_pointer;
759  std::function<void(std::shared_ptr<CompositeInstruction>, Args...)>
760  lambda_func;
761  std::shared_ptr<CompositeInstruction> parent_kernel;
762 
763  public:
764  // Here we set function_pointer to null and instead
765  // only use lambda_func. If we set lambda_func, function_pointer
766  // will never be used, so we should be good.
767  template <typename... CaptureArgs>
769  : function_pointer(*readOnly),
770  lambda_func([&](std::shared_ptr<CompositeInstruction> pp, Args... a) {
771  lambda(pp, a...);
772  }) {}
773 
774  KernelSignature(callable_function_ptr<Args...> &&f) : function_pointer(f) {}
775 
776  // CTor from a QCOR QuantumKernel instance:
777  template <
778  typename KernelType,
779  std::enable_if_t<
780  std::is_base_of_v<QuantumKernel<KernelType, Args...>, KernelType>,
781  bool> = true>
782  KernelSignature(KernelType &kernel)
783  : function_pointer(*readOnly),
784  lambda_func([&](std::shared_ptr<CompositeInstruction> pp, Args... a) {
785  // Expand the kernel and append to the *externally-provided*
786  // parent kernel as a KernelSignature one.
787  kernel.disable_destructor = true;
788  kernel(a...);
789  pp->addInstructions(kernel.parent_kernel->getInstructions());
790  }) {}
791 
792  // Ctor from raw void* function pointer.
793  // IMPORTANT: since function_pointer is kept as a *reference*,
794  // we must keep a reference to the original f_ptr void* as well.
795  KernelSignature(void *&f_ptr)
796  : function_pointer((callable_function_ptr<Args...> &)f_ptr) {}
797 
798  void operator()(std::shared_ptr<CompositeInstruction> ir, Args... args) {
799  if (lambda_func) {
800  lambda_func(ir, args...);
801  return;
802  }
803 
804  function_pointer(ir, args...);
805  }
806 
807  void operator()(Args... args) { operator()(parent_kernel, args...); }
808 
809  void set_parent_kernel(std::shared_ptr<CompositeInstruction> ir) {
810  parent_kernel = ir;
811  }
812 
813  void ctrl(std::shared_ptr<CompositeInstruction> ir,
814  const std::vector<qubit> &ctrl_qbits, Args... args) {
815  internal::apply_control<Args...>(ir, ctrl_qbits, *this, args...);
816  }
817 
818  void ctrl(std::shared_ptr<CompositeInstruction> ir,
819  const std::vector<int> ctrl_idxs, Args... args) {
820  std::vector<qubit> ctrl_qubit_vec;
821  for (int i = 0; i < ctrl_idxs.size(); i++) {
822  ctrl_qubit_vec.push_back(
823  {"q", static_cast<size_t>(ctrl_idxs[i]), nullptr});
824  }
825  ctrl(ir, ctrl_qubit_vec, args...);
826  }
827  void ctrl(std::shared_ptr<CompositeInstruction> ir, int ctrl_qbit,
828  Args... args) {
829  ctrl(ir, std::vector<int>{ctrl_qbit}, args...);
830  }
831 
832  void ctrl(std::shared_ptr<CompositeInstruction> ir, qubit ctrl_qbit,
833  Args... args) {
834  ctrl(ir, std::vector<qubit>{ctrl_qbit}, args...);
835  }
836 
837  void ctrl(std::shared_ptr<CompositeInstruction> ir, qreg ctrl_qbits,
838  Args... args) {
839  std::vector<qubit> ctrl_qubit_vec;
840  for (int i = 0; i < ctrl_qbits.size(); i++) {
841  ctrl_qubit_vec.push_back(ctrl_qbits[i]);
842  }
843  ctrl(ir, ctrl_qubit_vec, args...);
844  }
845 
846  void adjoint(std::shared_ptr<CompositeInstruction> ir, Args... args) {
847  internal::apply_adjoint<Args...>(ir, *this, args...);
848  }
849 
850  void print_kernel(std::ostream &os, Args... args) {
851  return internal::print_kernel<Args...>(*this, os, args...);
852  }
853 
854  void print_kernel(Args... args) { print_kernel(std::cout, args...); }
855 
856  std::size_t n_instructions(Args... args) {
857  return internal::n_instructions<Args...>(*this, args...);
858  }
859 
860  UnitaryMatrix as_unitary_matrix(Args... args) {
861  return internal::as_unitary_matrix<Args...>(*this, args...);
862  }
863 
864  std::string openqasm(Args... args) {
865  return internal::openqasm<Args...>(*this, args...);
866  }
867 
868  double observe(std::shared_ptr<Operator> obs, Args... args) {
869  return observe(*obs.get(), args...);
870  }
871 
872  double observe(Operator &obs, Args... args) {
873  return internal::observe<Args...>(obs, *this, args...);
874  }
875 };
876 
877 // Templated helper to attach parent_kernel to any
878 // KernelSignature arguments even nested in a std::vector<KernelSignature>
879 // The reason is that the Token Collector relies on a list of kernel names
880 // in the translation unit to attach parent_kernel to the operator() call.
881 // For KernelSignature provided in a container, tracking these at the
882 // TokenCollector level is error-prone (e.g. need to track any array accesses).
883 // Hence, we iterate over all kernel arguments and attach the parent_kernel
884 // to any KernelSignature argument at the top of each kernel's operator() call
885 // in a type-safe manner.
886 
887 // Last arg
888 inline void init_kernel_signature_args_impl(
889  std::shared_ptr<CompositeInstruction> ir) {}
890 template <typename T, typename... ArgsType>
891 void init_kernel_signature_args_impl(std::shared_ptr<CompositeInstruction> ir,
892  T &t, ArgsType &...Args);
893 
894 // Main function: to be added by the token collector at the beginning
895 // of each kernel operator().
896 template <typename... T>
897 void init_kernel_signature_args(std::shared_ptr<CompositeInstruction> ir,
898  T &...multi_inputs) {
899  init_kernel_signature_args_impl(ir, multi_inputs...);
900 }
901 
902 // Base case: generic type T,
903 // just ignore, proceed to the next arg.
904 template <typename T, typename... ArgsType>
905 void init_kernel_signature_args_impl(std::shared_ptr<CompositeInstruction> ir,
906  T &t, ArgsType &...Args) {
907  init_kernel_signature_args(ir, Args...);
908 }
909 
910 // Special case: this is a vector:
911 // iterate over all elements.
912 template <typename T, typename... ArgsType>
913 void init_kernel_signature_args_impl(std::shared_ptr<CompositeInstruction> ir,
914  std::vector<T> &vec_arg,
915  ArgsType... Args) {
916  for (auto &el : vec_arg) {
917  // Iterate the vector elements.
918  init_kernel_signature_args_impl(ir, el);
919  }
920  // Proceed with the rest.
921  init_kernel_signature_args(ir, Args...);
922 }
923 
924 // Handle KernelSignature arg => set the parent kernel.
925 template <typename... ArgsType>
926 void init_kernel_signature_args_impl(
927  std::shared_ptr<CompositeInstruction> ir,
928  KernelSignature<ArgsType...> &kernel_signature) {
929  kernel_signature.set_parent_kernel(ir);
930 }
931 
932 namespace internal {
933 // KernelSignature is the base of all kernel-like objects
934 // and we use it to implement kernel modifiers && utilities.
935 // Make this a utility function so that implicit conversion to KernelSignature
936 // occurs automatically.
937 template <typename... Args>
938 void apply_control(std::shared_ptr<CompositeInstruction> parent_kernel,
939  const std::vector<qubit> &ctrl_qbits,
940  KernelSignature<Args...> &kernelCallable, Args... args) {
941  std::vector<std::pair<std::string, size_t>> ctrl_qubits;
942  for (const auto &qb : ctrl_qbits) {
943  ctrl_qubits.emplace_back(std::make_pair(qb.first, qb.second));
944  }
945 
946  // Is is in a **compute** segment?
947  // i.e. doing control within the compute block itself.
948  // need to by-pass the compute marking in order for the control gate to
949  // work.
950  const bool cached_is_compute_section =
951  ::quantum::qrt_impl->isComputeSection();
952  if (cached_is_compute_section) {
953  ::quantum::qrt_impl->__end_mark_segment_as_compute();
954  }
955 
956  // Use the controlled gate module of XACC to transform
957  auto tempKernel = qcor::__internal__::create_composite("temp_control");
958  kernelCallable(tempKernel, args...);
959 
960  if (cached_is_compute_section) {
961  ::quantum::qrt_impl->__begin_mark_segment_as_compute();
962  }
963 
964  auto ctrlKernel = qcor::__internal__::create_and_expand_ctrl_u(
965  {{"U", tempKernel}, {"control-idx", ctrl_qubits}});
966  // ctrlKernel->expand({{"U", tempKernel}, {"control-idx", ctrl_qubits}});
967 
968  // Mark all the *Controlled* instructions as compute segment
969  // if it was in the compute_section.
970  // i.e. we have bypassed the marker previously to make C-U to work,
971  // now we mark all the generated instructions.
972  if (cached_is_compute_section) {
973  for (int instId = 0; instId < ctrlKernel->nInstructions(); ++instId) {
974  ctrlKernel->attachMetadata(instId,
975  {{"__qcor__compute__segment__", true}});
976  }
977  }
978 
979  for (int instId = 0; instId < ctrlKernel->nInstructions(); ++instId) {
980  parent_kernel->addInstruction(ctrlKernel->getInstruction(instId));
981  }
982  // Need to reset and point current program to the parent
983  quantum::set_current_program(parent_kernel);
984 }
985 
986 bool is_not_measure(std::shared_ptr<xacc::Instruction> inst);
987 std::vector<std::shared_ptr<xacc::Instruction>> handle_adjoint_instructions(
988  std::vector<std::shared_ptr<xacc::Instruction>>,
989  std::shared_ptr<CompositeInstruction>);
990 
991 template <typename... Args>
992 void apply_adjoint(std::shared_ptr<CompositeInstruction> parent_kernel,
993  KernelSignature<Args...> &kernelCallable, Args... args) {
994  auto tempKernel = qcor::__internal__::create_composite("temp_adjoint");
995  kernelCallable(tempKernel, args...);
996 
997  // get the instructions
998  auto instructions = tempKernel->getInstructions();
999  std::shared_ptr<CompositeInstruction> program = tempKernel;
1000 
1001  // Assert that we don't have measurement
1002  if (!std::all_of(instructions.cbegin(), instructions.cend(),
1003  [](const auto &inst) { return is_not_measure(inst); })) {
1004  error("Unable to create Adjoint for kernels that have Measure operations.");
1005  }
1006 
1007  auto new_instructions =
1008  internal::handle_adjoint_instructions(instructions, tempKernel);
1009 
1010  // add the instructions to the current parent kernel
1011  parent_kernel->addInstructions(std::move(new_instructions), false);
1012 
1013  ::quantum::set_current_program(parent_kernel);
1014 }
1015 
1016 template <typename... Args>
1017 double observe(Operator &obs, KernelSignature<Args...> &kernelCallable,
1018  Args... args) {
1019  auto tempKernel = qcor::__internal__::create_composite("temp_observe");
1020  kernelCallable(tempKernel, args...);
1021  auto instructions = tempKernel->getInstructions();
1022  // Assert that we don't have measurement
1023  if (!std::all_of(instructions.cbegin(), instructions.cend(),
1024  [](const auto &inst) { return is_not_measure(inst); })) {
1025  error("Unable to observe kernels that already have Measure operations.");
1026  }
1027 
1028  xacc::internal_compiler::execute_pass_manager();
1029 
1030  // Operator pre-processing, map down to Pauli and cache
1031  Operator observable;
1032  auto obs_str = obs.toString();
1033  std::hash<std::string> hasher;
1034  auto operator_hash = hasher(obs_str);
1035  if (__internal__::cached_observables.count(operator_hash)) {
1036  observable = __internal__::cached_observables[operator_hash];
1037  } else {
1038  if (obs_str.find("^") != std::string::npos) {
1039  auto fermionObservable = createOperator("fermion", obs_str);
1040  observable = operatorTransform("jw", fermionObservable);
1041 
1042  } else if (obs_str.find("X") != std::string::npos ||
1043  obs_str.find("Y") != std::string::npos ||
1044  obs_str.find("Z") != std::string::npos) {
1045  observable = createOperator("pauli", obs_str);
1046  }
1047  __internal__::cached_observables.insert({operator_hash, observable});
1048  }
1049 
1050  // Will fail to compile if more than one qreg is passed.
1051  std::tuple<Args...> tmp(std::forward_as_tuple(args...));
1052  auto q = std::get<qreg>(tmp);
1053  return qcor::observe(tempKernel, observable, q);
1054 }
1055 
1056 template <typename... Args>
1057 UnitaryMatrix as_unitary_matrix(KernelSignature<Args...> &kernelCallable,
1058  Args... args) {
1059  auto tempKernel = qcor::__internal__::create_composite("temp_as_unitary");
1060  kernelCallable(tempKernel, args...);
1061  auto instructions = tempKernel->getInstructions();
1062  // Assert that we don't have measurement
1063  if (!std::all_of(instructions.cbegin(), instructions.cend(),
1064  [](const auto &inst) { return is_not_measure(inst); })) {
1065  error(
1066  "Unable to compute unitary matrix for kernels that already have "
1067  "Measure operations.");
1068  }
1069 
1070  return __internal__::map_composite_to_unitary_matrix(tempKernel);
1071 }
1072 
1073 template <typename... Args>
1074 std::string openqasm(KernelSignature<Args...> &kernelCallable, Args... args) {
1075  auto tempKernel = qcor::__internal__::create_composite("temp_as_openqasm");
1076  kernelCallable(tempKernel, args...);
1077  xacc::internal_compiler::execute_pass_manager();
1078  return __internal__::translate("staq", tempKernel);
1079 }
1080 
1081 template <typename... Args>
1082 void print_kernel(KernelSignature<Args...> &kernelCallable, std::ostream &os,
1083  Args... args) {
1084  auto tempKernel = qcor::__internal__::create_composite("temp_print");
1085  kernelCallable(tempKernel, args...);
1086  xacc::internal_compiler::execute_pass_manager();
1087  os << tempKernel->toString() << "\n";
1088 }
1089 
1090 template <typename... Args>
1091 std::size_t n_instructions(KernelSignature<Args...> &kernelCallable,
1092  Args... args) {
1093  auto tempKernel = qcor::__internal__::create_composite("temp_count_insts");
1094  kernelCallable(tempKernel, args...);
1095  return tempKernel->nInstructions();
1096 }
1097 } // namespace internal
1098 } // namespace qcor
qcor::Operator
Definition: qcor_observable.hpp:24
qcor::KernelSignature
Definition: quantum_kernel.hpp:16
xacc::internal_compiler::qubit
Definition: qalloc.hpp:67
qcor::QJIT
Definition: qcor_jit.hpp:22
qcor::_qpu_lambda
Definition: quantum_kernel.hpp:327
qcor
Definition: qcor_syntax_handler.cpp:15
xacc::internal_compiler::qreg
Definition: qalloc.hpp:101
qcor::ArgsTranslator
Definition: qcor_utils.hpp:162
qcor::QuantumKernel
Definition: quantum_kernel.hpp:75
qcor::CompositeInstruction
Definition: qcor_ir.hpp:18