projekter/yquant

Frames around subcircuits obtained through foreach loops

Closed this issue · 4 comments

I am trying to port a quantikz circuit (picture below) to yquant. The great news is that I was able to use foreach and ifthenelse to obtain a few lines of codes. The bad one is that I don't know how to add, inside the circuit, the rectangular frames around part of the subcircuits (colored or not). Do you have any suggestion?

The code is shown below. Note that there are tiny differences between the two circuits. For example, instead of the X CNOT X gate of the original quantikz circuit, I simply used a CNOT gate with an inverted control. But this is not the relevant point of the discussion, just a tiny difference.

\documentclass[tikz]{standalone}
\usepackage{expl3} % load latex3 packages 
\usepackage[compat=0.4]{yquant}
\usepackage{ifthen}
\begin{document}
\begin{tikzpicture}
  \begin{yquant}
    qubit {$H[0,\idx]$} r0[4];
    qubit {$H[1,\idx]$} r1[4];
    qubit {$H[2,\idx]$} r2[4];
    qubit {$H[3,\idx]$} r3[4];
    qubit {$S[\idx]$} fs[3];
    qubit {$C[\idx]$} ca[6];

    \newcounter{fsn}
    \setcounter{fsn}{0}
    \newcounter{can}
    \setcounter{can}{0}

    \foreach \idx in {0,...,2} {
      \foreach \i in {\the\numexpr\idx+1, ..., 2} {
        \ifthenelse{\NOT \idx = 2}{
          \yquant
          cnot fs[\thefsn] | ~r\idx[\idx];
          \foreach \col in {0,...,3} {
            \yquant
            cnot r\idx[\col] | r\i[\col], fs[\thefsn];
          }
          \stepcounter{fsn}
        }
        {}
      }
      \foreach \i in {0, ..., 2} {
      \ifthenelse{\NOT \i = \idx}{
        \yquant
        cnot ca[\thecan] | r\i[\idx];
          \foreach \col in {0,...,3} {
            \yquant
            cnot r\i[\col] | r\idx[\col], ca[\thecan];
          }
        \stepcounter{can}
      }
        {}
      }
    }
  \end{yquant}
\end{tikzpicture}
\end{document}                                                                                                                                                                                                                                                                     

image

I do have some suggestions. Note that this is not a complete solution, but it should contain all the necessary ideas:

\documentclass[tikz]{standalone}
\usepackage[compat=0.6]{yquant}
\usetikzlibrary{backgrounds,quotes,fit}
\begin{document}
   \begin{tikzpicture}
      \begin{yquant}[operators/subcircuit/name mangling=transparent]
         qubit {$H[0,\idx]$} r0[4];
         qubit {$H[1,\idx]$} r1[4];
         qubit {$H[2,\idx]$} r2[4];
         qubit {$H[3,\idx]$} r3[4];
         qubit {$S[\idx]$} fs[3];
         qubit {$C[\idx]$} ca[6];
         
         \newcounter{fsn}
         \newcounter{can}
         \makeatletter
         
         \foreach \rowidx in {0, ..., 2} {
            \unless\ifnum\rowidx=2 %
               \yquant [this subcircuit box style={dashed, "Row \rowidx\space swaps"}] subcircuit {
                  qubit {} r0[4]; qubit {} r1[4]; qubit {} r2[4]; qubit {} r3[4];
                  qubit {} fs[3]; qubit {} ca[6];
                  \yquant@for \i := \numexpr\rowidx+1\relax to 2 {
                     \yquant cnot fs[\thefsn] ~ r\rowidx[\rowidx];
                     \foreach \colidx in {0, ..., 3} {
                        \yquant [global attrs/name/.expanded={sw\rowidx/\i/\colidx}]
                           cnot r\rowidx[\colidx] | r\i[\colidx], fs[\thefsn];
                     }
                     \stepcounter{fsn}
                  }
               } (-);
            \fi
            \yquant [this subcircuit box style={dashed, "Row \rowidx\space additions"}] subcircuit {
               qubit {} r0[4]; qubit {} r1[4]; qubit {} r2[4]; qubit {} r3[4];
               qubit {} fs[3]; qubit {} ca[6];
               \foreach \i in {0, ..., 2} {
                  \unless\ifnum\i=\rowidx\space
                     \yquant cnot ca[\thecan] | r\i[\rowidx];
                     \foreach \colidx in {0, ..., 3} {
                        \yquant [global attrs/name/.expanded={add\rowidx/\i/\colidx}]
                           cnot r\i[\colidx] | r\rowidx[\colidx], ca[\thecan];
                     }
                     \stepcounter{can}
                  \fi
               }
            } (-);
         }
      \end{yquant}
      \begin{scope}[on background layer, every node/.style={fill=red!10!white, draw, dashed}]
         \node[fit=(sw1/2/1) (sw1/2/1-p1)] {};
         \node[fit=(add1/0/1) (add1/0/1-p1)] {};
         \node[fit=(add1/2/1) (add1/2/1-p1) (add1/2/1-p0)] {};
         \node[fit=(add2/0/1) (add2/0/2) (add2/0/2-p1)] {};
         \node[fit=(add2/1/1) (add2/1/2) (add2/1/2-p1)] {};
      \end{scope}
   \end{tikzpicture}
\end{document}

So let me summarize the main points:

  • Do not use the macro \idx as your loop variable. yquant provides this macro for use within arguments to the gates, where it is filled with the current index of the register in a gate that acts on multiple registers. So while it is safe to use in register names (as you did), it is totally unsafe to use in arguments (as I did). Better not to use it at all.
  • In order to make the larger boxes, I enclosed the necessary parts in subcircuits, for which I defined the box styles appropriately. This is not a perfect solution, you'll see that the boxes are not aligned at the top, as some of the registers are larger, while others are smaller. Instead of using subcircuits, you may use the custom drawing approach that I employed for the backgrounds, then you can control this in a better way.
  • I also assign names to the gates that are automatically generated. Actually, this is a bit hacky. It should just work to say [name/.expanded=...], but apparently, the handler does not work well with attributes. I'll have to check whether this is a bug in yquant or the pgfkeys filtering library that I use. For this reason, I call the attribute with its full name /yquant/global attrs/name (which usually should never be done). Anyway, this results in all the gates being assigned names.
  • Since I am in a subcircuit, the names would not be available outside, so I use the feature introduced with version 0.5, name mangling=transparent.
  • You'll notice that in the first innermost loop, I replaced \foreach \i in {\numexpr\rowidx+1, ..., 2} by \yquant@for \i := \numexpr\rowidx+1 to 2 (and also had to say \makeatletter before so that this internal macro is usable). I did not plan to do this, but I got errors when using \i within the attribute names with the \foreach loop. Apparently, pgf (which provides \foreach) does some bad things when being passed something like {2, ..., 2}. Looks like a pgf bug, but I didn't investigate too much, I just replaced the loop by the one that yquant internally provides.
  • Also note that I shifted the checks for \rowidx <> 2 outside of the inner loop, which is more efficient.
  • After the circuit was drawn, I draw the backgrounds for the gates. For this, I use TikZ's layering functionality, namely the backgrounds library - we want the backgrounds to appear behind the gates, not on top (and using opacity for this would be a bad way). Then, I just fit all the background nodes to the appropriate named shapes. The name sw1/2/1 describes the "main" part of the gate, i.e., the NOT operation itself (if there was more than a single operation, I'd have to use sw/1/2/1-0, ... to select the correct one), the name sw1/2/1-p1 is the second positive control. Basically, you could add all the controls in here, but I chose just to select the absolutely necessary ones.
  • By the way, be aware of the syntax for negative controls. I looked to me like you wanted to prefix the name of a register by ~ in order to negate it. This is not the correct syntax. Instead, the tilde is the separator that starts the list of negative controls. (And in fact, it is not even necessary to write | if you don't have positive controls.)

@projekter Thanks for the detailed answer! It took me some time to absorb all the changes you made, but this is definitely pure gold.
For the alignment of the boxes, maybe it could be solved by adding a phantom X gate to the first qubit for every subcircuit? Is it possibile?

Yes and no. There is no such thing as an inbuilt phantom gate, so you'd have to fake it in some way. I can think of various ways to achieve this:

  • Instead of using subcircuits, draw the rectangles manually. This will also require some work, since you obviously cannot just name all the required nodes in the subcircuit and fit some rectangle to them - instead, you'd have to calculate the vertical positions by yourself, using some shared coordinates for all boxes.
  • Add a nobit above; and a nobit below; as the first and last registers and give them the attributes [register/minimum height=0pt, register/minimum depth=0pt]. Also add them to the subcircuits. This will ensure that all boxes are aligned, although there is a bit more space than strictly necessary.
  • Fake the phantom gate, as you suggested. You'd basically need something like [x radius=0pt, draw=none] not r0[0];. However:
    • Due to the draw=none, the line width of the NOT gate is not taken into account, therefore the height is not exactly as you want it to be.
    • You might say, if the width is zero, then can't I just draw the gate - unfortunately, you will then see the vertical line both from the middle of the CNOT as well as the circle that is now deformed into a line.
    • While the middle line can be easily eliminated by choosing a different shape, you will always get the outer border of whatever shape you choose (as long as it has a border - and if it doesn't have one, it will not occupy the space you want it to).
    • So you could replace the draw=none by opacity=0. The line is gone, but now you will see an interruption in the circuit wire that is exactly one linewidth large.
    • Still, we can make it work by some finetuning. We need draw=none, so that the wire continues, but we need the height to be larger by one linewidth. So just look up the dimensions. yquant has a default radius for the NOT of 1.3mm, TikZ has a default line width of 0.4pt, so you could just use y radius=1.3mm+0.2pt. This will do the job and work as your fake phantom gate: [x radius=0pt, y radius=1.3mm+0.2pt, draw=none] not r0[0];

@projekter thank you! I ended up using the nobit solution: easy and effective. But thank you again for all the accurate answers you provide!