Repository: Qiskit/qiskit

Issue:
Parameter does not support ufunc à la QASM
### What is the current behavior?

In order to emulate the following QASM, `Parameter` needs to apply along with trigonometry.
```
gate my_gate(phi,lambda) q
            {u1(cos(phi)/2) q;} 
```
A possible way, it would be using `sympy`
```
phi = Parameter('phi') 
circuit.u1(sympy.cos(phi/2), 0)
```
but this raises `CircuitError: 'invalid param type cos in instruction u1'`. Other libraries like `math` and `numpy` require a float-castable parameter. Same applies to the rest of the "QASM ufunc".

### Suggested solutions

Probably, the best solution is to extend `Parameter` type with these ufuncs. So the example would look like:
```
phi = Parameter('phi') 
circuit.u1((phi/2).cos(), 0)
```


Discussion:
From today's discussion, consensus was that its better for this to look something like
```
from qiskit.circuit.parameter import cos
circuit.u1(cos(phi/2), 0)
```
I'm new to qiskit, and I'm trying to make my first contribution. I'd like to give this one a go if you don't mind :-)
Hi @ymarathe , that sounds good. Since this is more involved than a typical first contribution, can you write up a short plan of how you'd expect to implement this? That way we can find any questions early in the process.
Thanks @kdk. 

Sure, here's what I have figured out so far:

- In parameter.py, extend Parameter class with trigonometric ufuncs (cos, sin)
- `__init__(self, parameter)` function in this class takes Parameter variable (parameter) as input, and calls `super.__init__ ()` [from the Parameter class] with name "cos(" + parameter.__str() + ")"
- With this, we will be able to do
`circuit.u1(cos(phi/2), 0)` in the example above
- Upon evaluation of `cos(phi/2)` sympy expression, we will get the desired output.

Right now, I'm stuck at figuring out how to do the parameter binding. Is `circuit.bind_parameters()` the only way to bind parameters?  If that's the case, is there any way we can setup a dependency from Parameter `phi` in the example above to the Parameter `cos(phi/2)`, so that when the user passes the value of `phi` using the parameter dict, `cos(phi/2)` get evaluated automatically. What would be the right place to do it?
>     * In parameter.py, extend Parameter class with trigonometric ufuncs (cos, sin)

The best place for these would be in the `ParameterExpression` class (though having them as separate functions in the parameter module would also make sense, since that's how we're planning for users to use them ref: https://github.com/Qiskit/qiskit-terra/issues/3908#issuecomment-594068143 ).

> 
>     * `__init__(self, parameter)` function in this class takes Parameter variable (parameter) as input, and calls `super.__init__ ()` [from the Parameter class] with name "cos(" + parameter.__str() + ")"
> 
>     * With this, we will be able to do
>       `circuit.u1(cos(phi/2), 0)` in the example above
> 
>     * Upon evaluation of `cos(phi/2)` sympy expression, we will get the desired output.
> 
> 
> Right now, I'm stuck at figuring out how to do the parameter binding. Is `circuit.bind_parameters()` the only way to bind parameters? If that's the case, is there any way we can setup a dependency from Parameter `phi` in the example above to the Parameter `cos(phi/2)`, so that when the user passes the value of `phi` using the parameter dict, `cos(phi/2)` get evaluated automatically. What would be the right place to do it?

Check the `ParameterExpression` class and how it handles the currently supported operations. In short, it is what handles the dependency between the qiskit `Parameter` instance, and the `sympy.Symbol` instance used in the expression. 

Resolving patch:
diff --git a/qiskit/circuit/parameterexpression.py b/qiskit/circuit/parameterexpression.py
--- a/qiskit/circuit/parameterexpression.py
+++ b/qiskit/circuit/parameterexpression.py
@@ -211,7 +211,6 @@ def _apply_operation(self, operation: Callable,
         Returns:
             A new expression describing the result of the operation.
         """
-
         self_expr = self._symbol_expr
 
         if isinstance(other, ParameterExpression):
@@ -261,6 +260,52 @@ def __truediv__(self, other):
     def __rtruediv__(self, other):
         return self._apply_operation(operator.truediv, other, reflected=True)
 
+    def _call(self, ufunc):
+        return ParameterExpression(
+            self._parameter_symbols,
+            ufunc(self._symbol_expr)
+        )
+
+    def sin(self):
+        """Sine of a ParameterExpression"""
+        from sympy import sin as _sin
+        return self._call(_sin)
+
+    def cos(self):
+        """Cosine of a ParameterExpression"""
+        from sympy import cos as _cos
+        return self._call(_cos)
+
+    def tan(self):
+        """Tangent of a ParameterExpression"""
+        from sympy import tan as _tan
+        return self._call(_tan)
+
+    def arcsin(self):
+        """Arcsin of a ParameterExpression"""
+        from sympy import asin as _asin
+        return self._call(_asin)
+
+    def arccos(self):
+        """Arccos of a ParameterExpression"""
+        from sympy import acos as _acos
+        return self._call(_acos)
+
+    def arctan(self):
+        """Arctan of a ParameterExpression"""
+        from sympy import atan as _atan
+        return self._call(_atan)
+
+    def exp(self):
+        """Exponential of a ParameterExpression"""
+        from sympy import exp as _exp
+        return self._call(_exp)
+
+    def log(self):
+        """Logarithm of a ParameterExpression"""
+        from sympy import log as _log
+        return self._call(_log)
+
     def __repr__(self):
         return '{}({})'.format(self.__class__.__name__, str(self))
 


Test patch:
diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py
--- a/test/python/circuit/test_parameters.py
+++ b/test/python/circuit/test_parameters.py
@@ -895,6 +895,44 @@ def test_copy_after_dot_data_setter(self):
         self.assertEqual(qc.parameters, set())
         raise_if_parameter_table_invalid(qc)
 
+    def test_circuit_with_ufunc(self):
+        """Test construction of circuit and binding of parameters
+        after we apply universal functions."""
+        from math import pi
+        phi = Parameter(name='phi')
+        theta = Parameter(name='theta')
+
+        qc = QuantumCircuit(2)
+        qc.u1(numpy.cos(phi), 0)
+        qc.u1(numpy.sin(phi), 0)
+        qc.u1(numpy.tan(phi), 0)
+        qc.rz(numpy.arccos(theta), 1)
+        qc.rz(numpy.arctan(theta), 1)
+        qc.rz(numpy.arcsin(theta), 1)
+
+        qc.assign_parameters({phi: pi, theta: 1},
+                             inplace=True)
+
+        qc_ref = QuantumCircuit(2)
+        qc_ref.u1(-1, 0)
+        qc_ref.u1(0, 0)
+        qc_ref.u1(0, 0)
+        qc_ref.rz(0, 1)
+        qc_ref.rz(pi / 4, 1)
+        qc_ref.rz(pi / 2, 1)
+
+        self.assertEqual(qc, qc_ref)
+
+    def test_compile_with_ufunc(self):
+        """Test compiling of circuit with unbounded parameters
+        after we apply universal functions."""
+        phi = Parameter('phi')
+        qc = QuantumCircuit(1)
+        qc.rx(numpy.cos(phi), 0)
+        backend = BasicAer.get_backend('qasm_simulator')
+        qc_aer = transpile(qc, backend)
+        self.assertIn(phi, qc_aer.parameters)
+
 
 def _construct_circuit(param, qr):
     qc = QuantumCircuit(qr)
@@ -1419,3 +1457,10 @@ def test_parameter_equal_to_identical_expression(self):
 
         self.assertEqual(expr, theta)
         self.assertEqual(theta, expr)
+
+    def test_parameter_symbol_equal_after_ufunc(self):
+        """Verfiy ParameterExpression phi
+        and ParameterExpression cos(phi) have the same symbol map"""
+        phi = Parameter('phi')
+        cos_phi = numpy.cos(phi)
+        self.assertEqual(phi._parameter_symbols, cos_phi._parameter_symbols)