Repository: Qiskit/qiskit

Issue:
to support data section in qobj to run the same circuits multiple times with different input data
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected behavior?
I expect that with the same circuits, I can run it multiple times with different input data. In this case, I don't need to recompile the circuits multiple times.  Every time when I want to run on new data, I just need to update the data section without updating other parts.
For example, in this qasm below, I had one operation "u3(data:input_magic1,0,pi) q1[0]" and a data section with foreach. So if it will execute all circuits for every different input_magic1, it will be very great.


OPENQASM 2.0;\ninclude \"qelib1.inc\";\nqreg q0[2];\nqreg q1[2];\ncreg c0[2];\ncreg c1[2];\nmeasure q1[1] -> c1[1];\nu3(data:input_magic1,0,pi) q1[0];\nmeasure q1[0] -> c1[0];\nu3(pi,0,pi) q0[1];\nmeasure q0[1] -> c0[1];\nu2(localinputvar1,0) q0[0];\nmeasure q0[0] -> c0[0];
section data: foreach (input_magic1: pi; input_magic1: 0.1; input_magic1: 0.2, input_magic1: 0.3)




Discussion:
I think qiskit-terra is the component that should deal with this. Everything related to compilation is Terra resposability. I'm (gonna try) moving this issue to qiskit-terra.
The idea is to allow this at the circuit level with parametrized circuits (currently under construction). So you would make a parametrized circuit, transpile it once, then drop in new parameters and run.

Resolving patch:
diff --git a/qiskit/circuit/instruction.py b/qiskit/circuit/instruction.py
--- a/qiskit/circuit/instruction.py
+++ b/qiskit/circuit/instruction.py
@@ -60,37 +60,14 @@ def __init__(self, name, num_qubits, num_clbits, params):
         self.num_qubits = num_qubits
         self.num_clbits = num_clbits
 
-        self.params = []  # a list of gate params stored
-        for single_param in params:
-            # example: u2(pi/2, sin(pi/4))
-            if isinstance(single_param, sympy.Basic):
-                self.params.append(single_param)
-            # example: OpenQASM parsed instruction
-            elif isinstance(single_param, node.Node):
-                self.params.append(single_param.sym())
-            # example: u3(0.1, 0.2, 0.3)
-            elif isinstance(single_param, (int, float)):
-                self.params.append(sympy.Number(single_param))
-            # example: Initialize([complex(0,1), complex(0,0)])
-            elif isinstance(single_param, complex):
-                self.params.append(single_param.real + single_param.imag * sympy.I)
-            # example: snapshot('label')
-            elif isinstance(single_param, str):
-                self.params.append(sympy.Symbol(single_param))
-            # example: numpy.array([[1, 0], [0, 1]])
-            elif isinstance(single_param, numpy.ndarray):
-                self.params.append(single_param)
-            # example: sympy.Matrix([[1, 0], [0, 1]])
-            elif isinstance(single_param, sympy.Matrix):
-                self.params.append(single_param)
-            else:
-                raise QiskitError("invalid param type {0} in instruction "
-                                  "{1}".format(type(single_param), name))
+        self._params = []  # a list of gate params stored
+
         # tuple (ClassicalRegister, int) when the instruction has a conditional ("if")
         self.control = None
         # list of instructions (and their contexts) that this instruction is composed of
         # empty definition means opaque or fundamental instruction
         self._definition = None
+        self.params = params
 
     def __eq__(self, other):
         """Two instructions are the same if they have the same name,
@@ -119,6 +96,51 @@ def _define(self):
         """Populates self.definition with a decomposition of this gate."""
         pass
 
+    @property
+    def params(self):
+        """return instruction params"""
+        # if params already defined don't attempt to get them from definition
+        if self._definition and not self._params:
+            self._params = []
+            for sub_instr, _, _ in self._definition:
+                self._params.extend(sub_instr.params)  # recursive call
+            return self._params
+        else:
+            return self._params
+
+    @params.setter
+    def params(self, parameters):
+        self._params = []
+        for single_param in parameters:
+            # example: u2(pi/2, sin(pi/4))
+            if isinstance(single_param, sympy.Basic):
+                self.params.append(single_param)
+            # example: OpenQASM parsed instruction
+            elif isinstance(single_param, node.Node):
+                self.params.append(single_param.sym())
+            # example: u3(0.1, 0.2, 0.3)
+            elif isinstance(single_param, (int, float)):
+                self.params.append(sympy.Number(single_param))
+            # example: Initialize([complex(0,1), complex(0,0)])
+            elif isinstance(single_param, complex):
+                self.params.append(single_param.real + single_param.imag * sympy.I)
+            # example: snapshot('label')
+            elif isinstance(single_param, str):
+                self.params.append(sympy.Symbol(single_param))
+            # example: numpy.array([[1, 0], [0, 1]])
+            elif isinstance(single_param, numpy.ndarray):
+                self.params.append(single_param)
+            # example: sympy.Matrix([[1, 0], [0, 1]])
+            elif isinstance(single_param, sympy.Matrix):
+                self.params.append(single_param)
+            elif isinstance(single_param, sympy.Expr):
+                self.params.append(single_param)
+            elif isinstance(single_param, numpy.number):
+                self.params.append(sympy.Number(single_param.item()))
+            else:
+                raise QiskitError("invalid param type {0} in instruction "
+                                  "{1}".format(type(single_param), self.name))
+
     @property
     def definition(self):
         """Return definition in terms of other basic gates."""
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -11,11 +11,13 @@
 import itertools
 import sys
 import multiprocessing as mp
+import sympy
 
 from qiskit.qasm.qasm import Qasm
 from qiskit.exceptions import QiskitError
 from .quantumregister import QuantumRegister
 from .classicalregister import ClassicalRegister
+from .variabletable import VariableTable
 
 
 class QuantumCircuit:
@@ -64,6 +66,9 @@ def __init__(self, *regs, name=None):
         self.cregs = []
         self.add_register(*regs)
 
+        # Variable table tracks instructions with variable parameters.
+        self._variable_table = VariableTable()
+
     def __str__(self):
         return str(self.draw(output='text'))
 
@@ -244,6 +249,21 @@ def append(self, instruction, qargs=None, cargs=None):
         # add the instruction onto the given wires
         instruction_context = instruction, qargs, cargs
         self.data.append(instruction_context)
+
+        # track variable parameters in instruction
+        for param_index, param in enumerate(instruction.params):
+            if isinstance(param, sympy.Expr):
+                current_symbols = set(self._variable_table.keys())
+                these_symbols = set(param.free_symbols)
+                new_symbols = these_symbols - current_symbols
+                common_symbols = these_symbols & current_symbols
+                if new_symbols:
+                    for symbol in new_symbols:
+                        self._variable_table[symbol] = [(instruction, param_index)]
+                if common_symbols:
+                    for symbol in common_symbols:
+                        self._variable_table[symbol].append((instruction, param_index))
+
         return instruction
 
     def _attach(self, instruction, qargs, cargs):
@@ -633,6 +653,30 @@ def from_qasm_str(qasm_str):
         qasm = Qasm(data=qasm_str)
         return _circuit_from_qasm(qasm)
 
+    @property
+    def variable_table(self):
+        """get the circuit variable table"""
+        return self._variable_table
+
+    @property
+    def variables(self):
+        """convenience function to get the variables defined in the variable table"""
+        return set(self._variable_table.keys())
+
+    def assign_variables(self, value_dict):
+        """Assign variables to values yielding a new circuit.
+
+        Args:
+            value_dict (dict): {variable: value, ...}
+
+        Returns:
+            QuantumCircuit: copy of self with assignment substitution.
+        """
+        new_circuit = self.copy()
+        for variable in value_dict:
+            new_circuit.variable_table[variable] = value_dict
+        return new_circuit
+
 
 def _circuit_from_qasm(qasm):
     # pylint: disable=cyclic-import
diff --git a/qiskit/circuit/variabletable.py b/qiskit/circuit/variabletable.py
new file mode 100644
--- /dev/null
+++ b/qiskit/circuit/variabletable.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2019, IBM.
+#
+# This source code is licensed under the Apache License, Version 2.0 found in
+# the LICENSE.txt file in the root directory of this source tree.
+"""
+Look-up table for varaible parameters in QuantumCircuit to support fast
+"""
+from collections import MutableMapping
+
+
+class VariableTable(MutableMapping):
+    """Class for managing and setting circuit variables"""
+
+    def __init__(self, *args, **kwargs):
+        """
+        the structure of _table is,
+           {var_object: [(instruction_object, parameter_index), ...]}
+        """
+        self._table = dict(*args, **kwargs)
+
+    def __getitem__(self, key):
+        return self._table[key]
+
+    def __setitem__(self, key, value):
+        """set the value of variable
+
+        Args:
+            key (Object): the variable to set
+            value (Number or dict): numeric constant or dictionary of
+                (Symbol:value) pairs
+        """
+        if key not in self._table:
+            self._table[key] = value
+        else:
+            for (instr, param_index) in self._table[key]:
+                params = instr.params
+                if isinstance(value, dict):
+                    params[param_index] = params[param_index].evalf(subs=value)
+                else:
+                    params[param_index] = value
+                instr.params = params
+
+    def __delitem__(self, key):
+        del self._table[key]
+
+    def __iter__(self):
+        return iter(self._table)
+
+    def __len__(self):
+        return len(self._table)


Test patch:
diff --git a/test/python/circuit/test_variable_parameters.py b/test/python/circuit/test_variable_parameters.py
new file mode 100644
--- /dev/null
+++ b/test/python/circuit/test_variable_parameters.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2019, IBM.
+#
+# This source code is licensed under the Apache License, Version 2.0 found in
+# the LICENSE.txt file in the root directory of this source tree.
+
+
+"""Test circuits with variable parameters."""
+import numpy
+import sympy
+
+from qiskit import BasicAer
+from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
+from qiskit.circuit import Gate
+from qiskit.transpiler import transpile
+from qiskit.compiler import assemble_circuits
+from qiskit.test import QiskitTestCase
+
+
+class TestVariableParameters(QiskitTestCase):
+    """QuantumCircuit Operations tests."""
+
+    def test_gate(self):
+        """Test instantiating gate with variable parmeters"""
+        theta = sympy.Symbol('θ')
+        theta_gate = Gate('test', 1, params=[theta])
+        self.assertEqual(theta_gate.name, 'test')
+        self.assertIsInstance(theta_gate.params[0], sympy.Symbol)
+
+    def test_compile_quantum_circuit(self):
+        """Test instantiating gate with variable parmeters"""
+        theta = sympy.Symbol('θ')
+        qr = QuantumRegister(1)
+        qc = QuantumCircuit(qr)
+        qc.rx(theta, qr)
+        backend = BasicAer.get_backend('qasm_simulator')
+        qc_aer = transpile(qc, backend)
+        qobj = assemble_circuits(qc_aer)
+        self.assertIn(theta, qobj.experiments[0].instructions[0].params)
+
+    def test_get_variables(self):
+        """Test instantiating gate with variable parmeters"""
+        from qiskit.extensions.standard.rx import RXGate
+        theta = sympy.Symbol('θ')
+        qr = QuantumRegister(1)
+        qc = QuantumCircuit(qr)
+        rxg = RXGate(theta)
+        qc.append(rxg, [qr[0]], [])
+        vparams = qc.variable_table
+        self.assertIs(theta, next(iter(vparams)))
+        self.assertIs(rxg, next(iter(next(iter(vparams[theta])))))
+
+    def test_fix_variable(self):
+        """Test setting a varaible to a constant value"""
+        theta = sympy.Symbol('θ')
+        qr = QuantumRegister(1)
+        qc = QuantumCircuit(qr)
+        qc.rx(theta, qr)
+        qc.u3(0, theta, 0, qr)
+        qc.variable_table[theta] = 0.5
+        self.assertEqual(qc.variable_table[theta][0][0].params[0], 0.5)
+        self.assertEqual(qc.variable_table[theta][1][0].params[1], 0.5)
+        qc.variable_table[theta] = 0.6
+        self.assertEqual(qc.variable_table[theta][0][0].params[0], 0.6)
+        self.assertEqual(qc.variable_table[theta][1][0].params[1], 0.6)
+
+    def test_multiple_variables(self):
+        """Test setting a varaible to a constant value"""
+        theta = sympy.Symbol('θ')
+        x = sympy.Symbol('x')
+        qr = QuantumRegister(1)
+        qc = QuantumCircuit(qr)
+        qc.rx(theta, qr)
+        qc.u3(0, theta, x, qr)
+        self.assertEqual(qc.variables, {theta, x})
+
+    def test_circuit_generation(self):
+        """Test creating a series of circuits parametrically"""
+        theta = sympy.Symbol('θ')
+        qr = QuantumRegister(1)
+        qc = QuantumCircuit(qr)
+        qc.rx(theta, qr)
+        backend = BasicAer.get_backend('qasm_simulator')
+        qc_aer = transpile(qc, backend)
+
+        # generate list of circuits
+        circs = []
+        theta_list = numpy.linspace(0, numpy.pi, 20)
+        for theta_i in theta_list:
+            circs.append(qc_aer.assign_variables({theta: theta_i}))
+        qobj = assemble_circuits(circs)
+        for index, theta_i in enumerate(theta_list):
+            self.assertEqual(qobj.experiments[index].instructions[0].params[0],
+                             theta_i)
+
+    def test_circuit_composition(self):
+        """Test preservation of variables when combining circuits."""
+        theta = sympy.Symbol('θ')
+        qr = QuantumRegister(1)
+        cr = ClassicalRegister(1)
+        qc1 = QuantumCircuit(qr)
+        qc1.rx(theta, qr)
+
+        phi = sympy.Symbol('phi')
+        qc2 = QuantumCircuit(qr, cr)
+        qc2.ry(phi, qr)
+        qc2.h(qr)
+        qc2.measure(qr, cr)
+
+        qc3 = qc1 + qc2
+        self.assertEqual(qc3.variables, {theta, phi})
+
+    def test_composite_instruction(self):
+        """Test preservation of variables when combining circuits."""
+        theta = sympy.Symbol('θ')
+        qr1 = QuantumRegister(1, name='qr1')
+        qc1 = QuantumCircuit(qr1)
+        qc1.rx(theta, qr1)
+        qc1.rz(numpy.pi/2, qr1)
+        qc1.ry(-theta, qr1)
+        gate = qc1.to_instruction()
+        self.assertEqual(gate.params, [theta, numpy.pi/2, -theta])
+
+        phi = sympy.Symbol('phi')
+        qr2 = QuantumRegister(3, name='qr2')
+        qc2 = QuantumCircuit(qr2)
+        qc2.ry(phi, qr2[0])
+        qc2.h(qr2)
+        qc2.append(gate, qargs=[qr2[1]])
+        self.assertEqual(qc2.variables, {theta, phi})
+
+    def test_parameter_expression(self):
+        """Test evaluation with parameter expressions"""
+        x = sympy.Symbol('x')
+        y = sympy.Symbol('y')
+        qr1 = QuantumRegister(1, name='qr1')
+        qc1 = QuantumCircuit(qr1)
+        qc1.rx(x, qr1)
+        qc1.rz(x + y, qr1)
+        qc1.ry(-x, qr1)
+        gate = qc1.to_instruction()
+        self.assertEqual(gate.params, [x, x+y, -x])
+
+        circs = []
+        x_list = numpy.arange(0, 5)
+        y_list = numpy.arange(10, 51, 10)
+        for ones, tens in zip(x_list, y_list):
+            circs.append(qc1.assign_variables({x: ones, y: tens}))
+        for index, (ones, tens) in enumerate(zip(x_list, y_list)):
+            self.assertEqual(circs[index].data[0][0].params[0], ones)
+            self.assertEqual(circs[index].data[1][0].params[0], ones + tens)
+            self.assertEqual(circs[index].data[2][0].params[0], -ones)
diff --git a/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle b/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle
Binary files a/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle differ
diff --git a/test/python/pickles/TestsBasicSwap_handle_measurement.pickle b/test/python/pickles/TestsBasicSwap_handle_measurement.pickle
Binary files a/test/python/pickles/TestsBasicSwap_handle_measurement.pickle and b/test/python/pickles/TestsBasicSwap_handle_measurement.pickle differ
diff --git a/test/python/pickles/TestsBasicSwap_initial_layout.pickle b/test/python/pickles/TestsBasicSwap_initial_layout.pickle
Binary files a/test/python/pickles/TestsBasicSwap_initial_layout.pickle and b/test/python/pickles/TestsBasicSwap_initial_layout.pickle differ
diff --git a/test/python/pickles/TestsLegacySwap_a_cx_to_map.pickle b/test/python/pickles/TestsLegacySwap_a_cx_to_map.pickle
Binary files a/test/python/pickles/TestsLegacySwap_a_cx_to_map.pickle and b/test/python/pickles/TestsLegacySwap_a_cx_to_map.pickle differ
diff --git a/test/python/pickles/TestsLegacySwap_handle_measurement.pickle b/test/python/pickles/TestsLegacySwap_handle_measurement.pickle
Binary files a/test/python/pickles/TestsLegacySwap_handle_measurement.pickle and b/test/python/pickles/TestsLegacySwap_handle_measurement.pickle differ
diff --git a/test/python/pickles/TestsLegacySwap_initial_layout.pickle b/test/python/pickles/TestsLegacySwap_initial_layout.pickle
Binary files a/test/python/pickles/TestsLegacySwap_initial_layout.pickle and b/test/python/pickles/TestsLegacySwap_initial_layout.pickle differ
diff --git a/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle b/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle
Binary files a/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle differ
diff --git a/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle b/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle
Binary files a/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle and b/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle differ
diff --git a/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle b/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle
Binary files a/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle and b/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle differ
diff --git a/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle b/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle
Binary files a/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle differ
diff --git a/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle b/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle
Binary files a/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle and b/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle differ
diff --git a/test/python/pickles/TestsStochasticSwap_initial_layout.pickle b/test/python/pickles/TestsStochasticSwap_initial_layout.pickle
Binary files a/test/python/pickles/TestsStochasticSwap_initial_layout.pickle and b/test/python/pickles/TestsStochasticSwap_initial_layout.pickle differ