Repository: Qiskit/qiskit

Issue:
add visualization of open controlled gates [mpl drawer]
@ewinston says in https://github.com/Qiskit/qiskit-terra/issues/3864:
> In PR #3739, open controlled gates were implemented. With that PR a new keyword argument was added to specify the control state,
> 
> `XGate().control(3, ctrl_state=6)`
> 
> This creates a 4 qubit gate where the first three qubits are controls and the fourth qubit is the target where the x-gate may be applied. A `ctrl_state` may alternatively be expressed as an integer string,
> 
> `XGate().control(3, ctrl_state='110')`
> 
> where it is more apparent which qubits are open and closed. In this case when q[0] is open and q[1] and q[2] are closed the x-gate will be applied to q[4].

As a reference https://github.com/Qiskit/qiskit-terra/pull/3867 implements open controlled gates for the text visualizer like this:
```python
qr = QuantumRegister(4, 'q')
circuit = QuantumCircuit(qr)
circuit.append(U2Gate(pi, -5 * pi / 8).control(3, ctrl_state='100'),
               [qr[0], qr[3], qr[2], qr[1]])
circuit.draw('text')
```
```
                         
q_0: |0>────────o────────
        ┌───────┴───────┐
q_1: |0>┤ U2(pi,-5pi/8) ├
        └───────┬───────┘
q_2: |0>────────■────────
                │        
q_3: |0>────────o────────
```
The current output for `circuit.draw('mpl')` is:
![image](https://user-images.githubusercontent.com/766693/76469326-72dc4700-63c4-11ea-896c-32f28a70d362.png)



Discussion:
Hi @ewinston , I am new to qiskit, but I would like to work on it if you don't mind. 
@1ucian0, I played around with this a bit and think I know how to make the changes. I'm not sure if @JayantiSingh is still interested, but if not, I can take it on.
Let's give Jayanti Singh <https://github.com/JayantiSingh> two or three
days for a ping. If silence, go ahead!

On Tue, Apr 7, 2020, 11:08 PM enavarro51 <notifications@github.com> wrote:

> @1ucian0 <https://github.com/1ucian0>, I played around with this a bit
> and think I know how to make the changes. I'm not sure if @JayantiSingh
> <https://github.com/JayantiSingh> is still interested, but if not, I can
> take it on.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/Qiskit/qiskit-terra/issues/3957#issuecomment-610726799>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAF3FZNMBVFTTFBNPXWOM43RLPTDBANCNFSM4LF7YWDQ>
> .
>

Haven't heard any pinging. Shall I proceed?
Sure! Let me know if you need any pointers!

On Fri, Apr 10, 2020, 11:33 AM Edwin Navarro <notifications@github.com>
wrote:

> Haven't heard any pinging. Shall I proceed?
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/Qiskit/qiskit-terra/issues/3957#issuecomment-612081741>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAF3FZIJ74WPM4DNTW3XLTDRL435DANCNFSM4LF7YWDQ>
> .
>

Thanks, Luciano. I'd already started working on it, and this is what I have done. Given this code,
```
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.extensions import U3Gate, U2Gate
import numpy as np

pi = np.pi
qr = QuantumRegister(4, 'q')
circuit = QuantumCircuit(qr)
circuit.append(U2Gate(pi/8,pi/8).control(3, ctrl_state=2),
               [qr[0], qr[3], qr[2], qr[1]])
circuit.append(U3Gate(3*pi/4,3*pi/4,3*pi/4).control(3, ctrl_state='101'),
               [qr[0], qr[3], qr[2], qr[1]])
circuit.draw(output='mpl')
```
![image](https://user-images.githubusercontent.com/16268251/79051758-da193100-7be6-11ea-87ce-c1f43e22d437.png)
Is this what you are looking for? I also have a couple of questions.
- Most of this was pretty straightforward, but the layer_width for these ControlledGates was not working correctly. Gates were jumping to very wide spacing. There were two sections in the code for layer_width and these gates fell into a default area. I decided not to change the other 2 sections, but  added a new section for these gates only. I did a lot of testing on the layer width to make sure the boxes were as close as possible horizontally without touching., There are a lot of possible combinations of these gates, though, and I'm wondering if additional QA would be in order.
- Since the output of this code is visual, I'm not sure how one would create a test for this. Any suggestions would help.
Thanks.

Resolving patch:
diff --git a/qiskit/visualization/matplotlib.py b/qiskit/visualization/matplotlib.py
--- a/qiskit/visualization/matplotlib.py
+++ b/qiskit/visualization/matplotlib.py
@@ -156,8 +156,7 @@ def __init__(self, qregs, cregs, ops,
             self.ax = ax
             self.figure = ax.get_figure()
 
-        # TODO: self._style.fold should be removed after deprecation
-        self.fold = self._style.fold or fold
+        self.fold = fold
         if self.fold < 2:
             self.fold = -1
 
@@ -296,7 +295,6 @@ def _gate(self, xy, fc=None, wide=False, text=None, subtext=None):
                 disp_text = "${}$".format(self._style.disptex[text])
             else:
                 disp_text = text
-
             if subtext:
                 self.ax.text(xpos, ypos + 0.15 * HIG, disp_text, ha='center',
                              va='center', fontsize=font_size,
@@ -426,6 +424,15 @@ def _ctrl_qubit(self, xy, fc=None, ec=None):
                              linewidth=1.5, zorder=PORDER_GATE)
         self.ax.add_patch(box)
 
+    def set_multi_ctrl_bits(self, ctrl_state, num_ctrl_qubits, qbit, color_str):
+        # convert op.ctrl_state to bit string and reverse
+        cstate = "{0:b}".format(ctrl_state).rjust(num_ctrl_qubits, '0')[::-1]
+        for i in range(num_ctrl_qubits):
+            # Make facecolor of ctrl bit the box color if closed and bkgrnd if open
+            fc_open_close = (self._style.dispcol[color_str] if cstate[i] == '1'
+                             else self._style.bg)
+            self._ctrl_qubit(qbit[i], fc=fc_open_close, ec=self._style.dispcol[color_str])
+
     def _tgt_qubit(self, xy, fc=None, ec=None, ac=None,
                    add_width=None):
         if self._style.gc != DefaultStyle().gc:
@@ -460,9 +467,9 @@ def _tgt_qubit(self, xy, fc=None, ec=None, ac=None,
                      [ypos, ypos], color=ac, linewidth=linewidth,
                      zorder=PORDER_GATE + 1)
 
-    def _swap(self, xy):
+    def _swap(self, xy, color):
         xpos, ypos = xy
-        color = self._style.dispcol['swap']
+
         self.ax.plot([xpos - 0.20 * WID, xpos + 0.20 * WID],
                      [ypos - 0.20 * WID, ypos + 0.20 * WID],
                      color=color, linewidth=2, zorder=PORDER_LINE + 1)
@@ -647,7 +654,7 @@ def _draw_regs_sub(self, n_fold, feedline_l=False, feedline_r=False):
                                  - n_fold * (self._cond['n_lines'] + 1)))
 
     def _draw_ops(self, verbose=False):
-        _wide_gate = ['u2', 'u3', 'cu2', 'cu3', 'unitary', 'r']
+        _wide_gate = ['u2', 'u3', 'cu3', 'unitary', 'r', 'cu1', 'rzz']
         _barriers = {'coord': [], 'group': []}
 
         #
@@ -671,7 +678,7 @@ def _draw_ops(self, verbose=False):
             layer_width = 1
 
             for op in layer:
-
+                # If one of the standard wide gates
                 if op.name in _wide_gate:
                     if layer_width < 2:
                         layer_width = 2
@@ -696,6 +703,22 @@ def _draw_ops(self, verbose=False):
                                     layer_width = 2
                             continue
 
+                # If custom ControlledGate
+                elif isinstance(op.op, ControlledGate) and op.name not in [
+                        'ccx', 'cx', 'c3x', 'c4x', 'cy', 'cz', 'ch', 'cu1',
+                        'cu3', 'crz', 'cswap']:
+                    if op.type == 'op' and hasattr(op.op, 'params'):
+                        param = self.param_parse(op.op.params)
+                        if '$\\pi$' in param:
+                            pi_count = param.count('pi')
+                            len_param = len(param) - (4 * pi_count)
+                        else:
+                            len_param = len(param)
+                        if len_param > len(op.name):
+                            box_width = math.floor(len_param / 5.5)
+                            layer_width = box_width
+                            continue
+
                 # if custom gate with a longer than standard name determine
                 # width
                 elif op.name not in ['barrier', 'snapshot', 'load', 'save',
@@ -727,7 +750,6 @@ def _draw_ops(self, verbose=False):
             this_anc = prev_anc + 1
 
             for op in layer:
-
                 _iswide = op.name in _wide_gate
                 if op.name not in ['barrier', 'snapshot', 'load', 'save',
                                    'noise', 'cswap', 'swap', 'measure',
@@ -841,23 +863,26 @@ def _draw_ops(self, verbose=False):
                     self._custom_multiqubit_gate(q_xy, wide=_iswide,
                                                  text="Unitary")
                 elif isinstance(op.op, ControlledGate) and op.name not in [
-                        'ccx', 'cx', 'cy', 'cz', 'ch', 'cu1', 'cu3', 'crz',
+                        'ccx', 'cx', 'c3x', 'c4x', 'cy', 'cz', 'ch', 'cu1', 'cu3', 'crz',
                         'cswap']:
                     disp = op.op.base_gate.name
                     num_ctrl_qubits = op.op.num_ctrl_qubits
                     num_qargs = len(q_xy) - num_ctrl_qubits
+                    # set the ctrl qbits to open or closed
+                    self.set_multi_ctrl_bits(op.op.ctrl_state, num_ctrl_qubits, q_xy, 'multi')
 
-                    for i in range(num_ctrl_qubits):
-                        self._ctrl_qubit(q_xy[i], fc=self._style.dispcol['multi'],
-                                         ec=self._style.dispcol['multi'])
                     # add qubit-qubit wiring
                     self._line(qreg_b, qreg_t, lc=self._style.dispcol['multi'])
-                    if disp == 'z':
-                        self._ctrl_qubit(q_xy[i+1], fc=self._style.dispcol['multi'],
-                                         ec=self._style.dispcol['multi'])
-                    elif num_qargs == 1:
-                        self._gate(q_xy[-1], wide=_iswide, fc=self._style.dispcol['multi'],
-                                   text=disp)
+                    if num_qargs == 1:
+                        if param:
+                            self._gate(q_xy[num_ctrl_qubits], wide=_iswide,
+                                       text=disp,
+                                       fc=self._style.dispcol['multi'],
+                                       subtext='{}'.format(param))
+                        else:
+                            fcx = op.name if op.name in self._style.dispcol else 'multi'
+                            self._gate(q_xy[num_ctrl_qubits], wide=_iswide, text=disp,
+                                       fc=self._style.dispcol[fcx])
                     else:
                         self._custom_multiqubit_gate(
                             q_xy[num_ctrl_qubits:], wide=_iswide, fc=self._style.dispcol['multi'],
@@ -883,8 +908,10 @@ def _draw_ops(self, verbose=False):
                             add_width = self._style.colored_add_width
                         else:
                             add_width = None
-                        self._ctrl_qubit(q_xy[0], fc=self._style.dispcol['cx'],
-                                         ec=self._style.dispcol['cx'])
+                        num_ctrl_qubits = op.op.num_ctrl_qubits
+                        # set the ctrl qbits to open or closed
+                        self.set_multi_ctrl_bits(op.op.ctrl_state, num_ctrl_qubits, q_xy, 'cx')
+
                         if self._style.name != 'bw':
                             self._tgt_qubit(q_xy[1], fc=self._style.dispcol['cx'],
                                             ec=self._style.dispcol['cx'],
@@ -942,26 +969,29 @@ def _draw_ops(self, verbose=False):
 
                     # rzz gate
                     elif op.name == 'rzz':
-                        self._ctrl_qubit(q_xy[0])
-                        self._ctrl_qubit(q_xy[1])
+                        color = self._style.dispcol['multi']
+                        self._ctrl_qubit(q_xy[0], fc=color, ec=color)
+                        self._ctrl_qubit(q_xy[1], fc=color, ec=color)
                         self._sidetext(qreg_b, text='zz({})'.format(param))
 
                         # add qubit-qubit wiring
-                        self._line(qreg_b, qreg_t)
+                        self._line(qreg_b, qreg_t, lc=color)
 
                     # cu1 gate
                     elif op.name == 'cu1':
-                        self._ctrl_qubit(q_xy[0])
-                        self._ctrl_qubit(q_xy[1])
+                        color = self._style.dispcol['multi']
+                        self._ctrl_qubit(q_xy[0], fc=color, ec=color)
+                        self._ctrl_qubit(q_xy[1], fc=color, ec=color)
                         self._sidetext(qreg_b, text='U1 ({})'.format(param))
 
                         # add qubit-qubit wiring
-                        self._line(qreg_b, qreg_t)
+                        fc = self._style
+                        self._line(qreg_b, qreg_t, lc=color)
 
                     # swap gate
                     elif op.name == 'swap':
-                        self._swap(q_xy[0])
-                        self._swap(q_xy[1])
+                        self._swap(q_xy[0], self._style.dispcol['swap'])
+                        self._swap(q_xy[1], self._style.dispcol['swap'])
                         # add qubit-qubit wiring
                         self._line(qreg_b, qreg_t, lc=self._style.dispcol['swap'])
 
@@ -977,28 +1007,27 @@ def _draw_ops(self, verbose=False):
                 #
                 # draw multi-qubit gates (n=3)
                 #
-                elif len(q_xy) == 3:
+                elif len(q_xy) in range(3, 6):
                     # cswap gate
                     if op.name == 'cswap':
                         self._ctrl_qubit(q_xy[0],
                                          fc=self._style.dispcol['multi'],
                                          ec=self._style.dispcol['multi'])
-                        self._swap(q_xy[1])
-                        self._swap(q_xy[2])
+                        self._swap(q_xy[1], self._style.dispcol['multi'])
+                        self._swap(q_xy[2], self._style.dispcol['multi'])
                         # add qubit-qubit wiring
                         self._line(qreg_b, qreg_t, lc=self._style.dispcol['multi'])
                     # ccx gate
-                    elif op.name == 'ccx':
-                        self._ctrl_qubit(q_xy[0], fc=self._style.dispcol['multi'],
-                                         ec=self._style.dispcol['multi'])
-                        self._ctrl_qubit(q_xy[1], fc=self._style.dispcol['multi'],
-                                         ec=self._style.dispcol['multi'])
+                    elif op.name == 'ccx' or op.name == 'c3x' or op.name == 'c4x':
+                        num_ctrl_qubits = op.op.num_ctrl_qubits
+                        # set the ctrl qbits to open or closed
+                        self.set_multi_ctrl_bits(op.op.ctrl_state, num_ctrl_qubits, q_xy, 'multi')
                         if self._style.name != 'bw':
-                            self._tgt_qubit(q_xy[2], fc=self._style.dispcol['multi'],
+                            self._tgt_qubit(q_xy[num_ctrl_qubits], fc=self._style.dispcol['multi'],
                                             ec=self._style.dispcol['multi'],
                                             ac=self._style.dispcol['target'])
                         else:
-                            self._tgt_qubit(q_xy[2], fc=self._style.dispcol['target'],
+                            self._tgt_qubit(q_xy[num_ctrl_qubits], fc=self._style.dispcol['target'],
                                             ec=self._style.dispcol['multi'],
                                             ac=self._style.dispcol['multi'])
                         # add qubit-qubit wiring
@@ -1009,7 +1038,7 @@ def _draw_ops(self, verbose=False):
                                                      text=op.name)
 
                 # draw custom multi-qubit gate
-                elif len(q_xy) > 3:
+                elif len(q_xy) > 5:
                     self._custom_multiqubit_gate(q_xy, c_xy, wide=_iswide,
                                                  text=op.name)
                 else:
diff --git a/qiskit/visualization/qcstyle.py b/qiskit/visualization/qcstyle.py
--- a/qiskit/visualization/qcstyle.py
+++ b/qiskit/visualization/qcstyle.py
@@ -96,7 +96,6 @@ def __init__(self):
             'meas': non_gate_color
         }
         self.latexmode = False
-        self.fold = None  # To be removed after 0.10 is released
         self.bundle = True
         self.index = False
         self.figwidth = -1
@@ -125,12 +124,6 @@ def set_style(self, style_dic):
         self.dpi = dic.pop('dpi', self.dpi)
         self.margin = dic.pop('margin', self.margin)
         self.cline = dic.pop('creglinestyle', self.cline)
-        if 'fold' in dic:
-            warn('The key "fold" in the argument "style" is being replaced by the argument "fold"',
-                 DeprecationWarning, 5)
-            self.fold = dic.pop('fold', self.fold)
-            if self.fold < 2:
-                self.fold = -1
 
         if dic:
             warn('style option/s ({}) is/are not supported'.format(', '.join(dic.keys())),
@@ -200,7 +193,6 @@ def __init__(self):
             'multi': '#000000'
         }
         self.latexmode = False
-        self.fold = 25
         self.bundle = True
         self.index = False
         self.figwidth = -1
@@ -231,12 +223,6 @@ def set_style(self, style_dic):
         self.dpi = dic.pop('dpi', self.dpi)
         self.margin = dic.pop('margin', self.margin)
         self.cline = dic.pop('creglinestyle', self.cline)
-        if 'fold' in dic:
-            warn('The key "fold" in the argument "style" is being replaced by the argument "fold"',
-                 DeprecationWarning, 5)
-            self.fold = dic.pop('fold', self.fold)
-            if self.fold < 2:
-                self.fold = -1
 
         if dic:
             warn('style option/s ({}) is/are not supported'.format(', '.join(dic.keys())),


Test patch: