text: # This code is part of Qiskit.
  #
  # (C) Copyright IBM 2017, 2019.
  #
  # This code is licensed under the Apache License, Version 2.0. You may
  # obtain a copy of this license in the LICENSE.txt file in the root directory
  # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
  #
  # Any modifications or derivative works of this code must retain this
  # copyright notice, and modified files need to carry a notice indicating
  # that they have been altered from the originals.
  
  # pylint: disable=invalid-name
  
  """Parser for mathematical string expressions returned by backends."""
  from typing import Dict, List, Union
  import ast
  import copy
  import operator
  
  import cmath
  
  from qiskit.pulse.exceptions import PulseError
  from qiskit.circuit import ParameterExpression
  
  
  class PulseExpression(ast.NodeTransformer):
      """Expression parser to evaluate parameter values."""
  
      _math_ops = {
          "acos": cmath.acos,
          "acosh": cmath.acosh,
          "asin": cmath.asin,
          "asinh": cmath.asinh,
          "atan": cmath.atan,
          "atanh": cmath.atanh,
          "cos": cmath.cos,
          "cosh": cmath.cosh,
          "exp": cmath.exp,
          "log": cmath.log,
          "log10": cmath.log10,
          "sin": cmath.sin,
          "sinh": cmath.sinh,
          "sqrt": cmath.sqrt,
          "tan": cmath.tan,
          "tanh": cmath.tanh,
          "pi": cmath.pi,
          "e": cmath.e,
      }
      """Valid math functions."""
  
      _binary_ops = {
          ast.Add: operator.add,
          ast.Sub: operator.sub,
          ast.Mult: operator.mul,
          ast.Div: operator.truediv,
          ast.Pow: operator.pow,
      }
      """Valid binary operations."""
  
      _unary_ops = {ast.UAdd: operator.pos, ast.USub: operator.neg}
      """Valid unary operations."""
  
      def __init__(self, source: Union[str, ast.Expression], partial_binding: bool = False):
          """Create new evaluator.
  
          Args:
              source: Expression of equation to evaluate.
              partial_binding: Allow partial bind of parameters.
  
          Raises:
              PulseError: When invalid string is specified.
          """
          self._partial_binding = partial_binding
          self._locals_dict = {}
          self._params = set()
  
          if isinstance(source, ast.Expression):
              self._tree = source
          else:
              try:
                  self._tree = ast.parse(source, mode="eval")
              except SyntaxError as ex:
                  raise PulseError(f"{source} is invalid expression.") from ex
  
          # parse parameters
          self.visit(self._tree)
  
      @property
      def params(self) -> List[str]:
          """Get parameters.
  
          Returns:
              A list of parameters in sorted order.
          """
          return sorted(self._params.copy())
  
      def __call__(self, *args, **kwargs) -> Union[float, complex, ast.Expression]:
          """Evaluate the expression with the given values of the expression's parameters.
  
          Args:
              *args: Variable length parameter list.
              **kwargs: Arbitrary parameters.
  
          Returns:
              Evaluated value.
  
          Raises:
              PulseError: When parameters are not bound.
          """
          if isinstance(self._tree.body, (ast.Constant, ast.Num)):
              return self._tree.body.n
  
          self._locals_dict.clear()
          if args:
              for key, val in zip(self.params, args):
                  self._locals_dict[key] = val
          if kwargs:
              for key, val in kwargs.items():
                  if key in self.params:
                      if key not in self._locals_dict.keys():
                          self._locals_dict[key] = val
                      else:
                          raise PulseError(
                              "%s got multiple values for argument '%s'"
                              % (self.__class__.__name__, key)
                          )
                  else:
                      raise PulseError(
                          "%s got an unexpected keyword argument '%s'"
                          % (self.__class__.__name__, key)
                      )
  
          expr = self.visit(self._tree)
  
          if not isinstance(expr.body, (ast.Constant, ast.Num)):
              if self._partial_binding:
                  return PulseExpression(expr, self._partial_binding)
              else:
                  raise PulseError("Parameters %s are not all bound." % self.params)
          return expr.body.n
  
      @staticmethod
      def _match_ops(opr: ast.AST, opr_dict: Dict, *args) -> Union[float, complex]:
          """Helper method to apply operators.
  
          Args:
              opr: Operator of node.
              opr_dict: Mapper from ast to operator.
              *args: Arguments supplied to operator.
  
          Returns:
              Evaluated value.
  
          Raises:
              PulseError: When unsupported operation is specified.
          """
          for op_type, op_func in opr_dict.items():
              if isinstance(opr, op_type):
                  return op_func(*args)
          raise PulseError("Operator %s is not supported." % opr.__class__.__name__)
  
      def visit_Expression(self, node: ast.Expression) -> ast.Expression:
          """Evaluate children nodes of expression.
  
          Args:
              node: Expression to evaluate.
  
          Returns:
              Evaluated value.
          """
          tmp_node = copy.deepcopy(node)
          tmp_node.body = self.visit(tmp_node.body)
  
          return tmp_node
  
      def visit_Num(self, node: ast.Num) -> ast.Num:
          """Return number as it is.
  
          Args:
              node: Number.
  
          Returns:
              Input node.
          """
          # node that Num node is deprecated in Python 3.8.
          # Constant node is recommended.
          return node
  
      def visit_Constant(self, node: ast.Constant) -> ast.Constant:
          """Return constant value as it is.
  
          Args:
              node: Constant.
  
          Returns:
              Input node.
          """
          return node
  
      def visit_Name(self, node: ast.Name) -> Union[ast.Name, ast.Constant]:
          """Evaluate name and return ast.Constant if it is bound.
  
          Args:
              node: Name to evaluate.
  
          Returns:
              Evaluated value.
  
          Raises:
              PulseError: When parameter value is not a number.
          """
          if node.id in self._math_ops.keys():
              val = ast.Constant(n=self._math_ops[node.id])
              return ast.copy_location(val, node)
          elif node.id in self._locals_dict.keys():
              _val = self._locals_dict[node.id]
              if not isinstance(_val, ParameterExpression):
                  # check value type
                  try:
                      _val = complex(_val)
                      if not _val.imag:
                          _val = _val.real
                  except ValueError as ex:
                      raise PulseError(
                          f"Invalid parameter value {node.id} = {self._locals_dict[node.id]} is "
                          "specified."
                      ) from ex
              val = ast.Constant(n=_val)
              return ast.copy_location(val, node)
          self._params.add(node.id)
          return node
  
      def visit_UnaryOp(self, node: ast.UnaryOp) -> Union[ast.UnaryOp, ast.Constant]:
          """Evaluate unary operation and return ast.Constant if operand is bound.
  
          Args:
              node: Unary operation to evaluate.
  
          Returns:
              Evaluated value.
          """
          node.operand = self.visit(node.operand)
          if isinstance(node.operand, (ast.Constant, ast.Num)):
              val = ast.Constant(n=self._match_ops(node.op, self._unary_ops, node.operand.n))
              return ast.copy_location(val, node)
          return node
  
      def visit_BinOp(self, node: ast.BinOp) -> Union[ast.BinOp, ast.Constant]:
          """Evaluate binary operation and return ast.Constant if operands are bound.
  
          Args:
              node: Binary operation to evaluate.
  
          Returns:
              Evaluated value.
          """
          node.left = self.visit(node.left)
          node.right = self.visit(node.right)
          if isinstance(node.left, (ast.Constant, ast.Num)) and isinstance(
              node.right, (ast.Constant, ast.Num)
          ):
              val = ast.Constant(
                  n=self._match_ops(node.op, self._binary_ops, node.left.n, node.right.n)
              )
              return ast.copy_location(val, node)
          return node
  
      def visit_Call(self, node: ast.Call) -> Union[ast.Call, ast.Constant]:
          """Evaluate function and return ast.Constant if all arguments are bound.
  
          Args:
              node: Function to evaluate.
  
          Returns:
              Evaluated value.
  
          Raises:
              PulseError: When unsupported or unsafe function is specified.
          """
          if not isinstance(node.func, ast.Name):
              raise PulseError("Unsafe expression is detected.")
          node.args = [self.visit(arg) for arg in node.args]
          if all(isinstance(arg, (ast.Constant, ast.Num)) for arg in node.args):
              if node.func.id not in self._math_ops.keys():
                  raise PulseError("Function %s is not supported." % node.func.id)
              _args = [arg.n for arg in node.args]
              _val = self._math_ops[node.func.id](*_args)
              if not _val.imag:
                  _val = _val.real
              val = ast.Constant(n=_val)
              return ast.copy_location(val, node)
          return node
  
      def generic_visit(self, node):
          raise PulseError("Unsupported node: %s" % node.__class__.__name__)
  
  
  def parse_string_expr(source: str, partial_binding: bool = False):
      """Safe parsing of string expression.
  
      Args:
          source: String expression to parse.
          partial_binding: Allow partial bind of parameters.
  
      Returns:
          PulseExpression: Returns a expression object.
  
      Example:
  
          expr = 'P1 + P2 + P3'
          parsed_expr = parse_string_expr(expr, partial_binding=True)
  
          # create new PulseExpression
          bound_two = parsed_expr(P1=1, P2=2)
          # evaluate expression
          value1 = bound_two(P3=3)
          value2 = bound_two(P3=4)
          value3 = bound_two(P3=5)
  
      """
      subs = [("numpy.", ""), ("np.", ""), ("math.", ""), ("cmath.", "")]
      for match, sub in subs:
          source = source.replace(match, sub)
  
      return PulseExpression(source, partial_binding)
  