Skip to content

Arithmetic Expressions

The Arithmetic function allows one to write complex mathematical expression in free format. The notation follows the 'python' language for math notation.

The function first parses the expression and builds abstract syntax tree (AST). Then, the Classiq engine finds a computation strategy for a specified number of qubits, and compiles the desired circuit.

The main difference between evaluating an arithmetic expression on a classical computer and a quantum computer is that all calculations done on the quantum computer are reversible. Therefore, all intermediate computation results must be stored in a quantum register. Qubits that are not freed cannot be used later on in the circuit.

Analogously to the classical world, there is a form of quantum "garbage collection", usually referred to as uncomputation, which returns the garbage qubits to their original state. The order in which qubits are released and reused is determined by the computation strategy. By employing different strategies, one may produce a variety of circuits with the same functionality. In general, longer circuits require less qubits than shorter ones. Several types of strategies are available on the Classiq platform. The naive strategy[1] traverses over the abstract syntax tree in topological order, The optimized strategy chooses a more complicated order of computations and uncomputations, given constraints. It is also possible to set the uncomputation method to 'no_uncomputation', avoiding uncomputations altogether, leaving qubits dirty with intermediate results. Similarly, the 'dirty_optimized' strategy gives the optimized solution, without post-result uncomputations.

Besides computation strategies, one may choose whether to allow input override, which may save both qubits and depth. This is done using the inputs_to_save field, defaulted to the empty set. Every input specified in this set will be available as an output of the arithmetic function. Inputs are specified by their names, which should match keys from the definitions field.

Furthermore, many arithmetic simplifications are available through the 'simplify' parameter, allowing substantial shortening of the circuit. Among them are methods such as collecting terms, replacing operations, order commuting operations and many more.

Supported operators:

  • Add '+'
  • Subtract '-'
  • Multiplication '*'
  • Bitwise Or, '|'
  • Bitwise And, '&'
  • Bitwise Xor, '^'
  • Invert, '~'
  • Equal , '=='
  • Not Equal, '!='
  • Greater Than, '>'
  • Greater Or Equal, '>='
  • Less Than, '<'
  • Less Or Equal, '<='
  • Modulo , '%' *limited for power of 2
  • Logical And , 'and'
  • Logical Or , 'or'
  • Right Bit Shift, '>>'
  • Left Bit Shift, '<<'
  • Cyclic Right Bit Shift, 'CRShift'
  • Cyclic Left Bit Shift, 'CLShift'
  • Max, 'max' *(n>=2 arguments)
  • Min, 'min' *(n>=2 arguments)

Additional operators can be easily defined by the user.

Syntax

Function: Arithmetic

Parameters:

  • expression: str
  • definitions: Dict[str, Union[int, float, FixPointNumber, RegisterUserInput]
  • uncomputation_method: ['naive', 'optimized', 'none']
  • inputs_to_save: Set[str]
  • qubit_count: Optional[PositiveInt]
  • simplify: bool
  • output_name: Optional[str]
  • max_fraction_places: Optional[PositiveInt]
{
  "function": "Arithmetic",
  "function_params": {
    "expression": "a ^ 3 + b + (2 - c % 4) - max(a, b, -c)",
    "definitions": {
      "a": {
        "size": 2
      },
      "b": {
        "size": 1
      },
      "c": {
        "size": 3
      }
    },
    "uncomputation_method": "optimized",
    "qubit_count": 25
  }
}

Register Names

The names of the input registers are determined by the definitions field. Each defined variable has a corresponding input register with the same name.

The result is then placed in the expression_result output register. If the allow_input_override flag is set to False, all inputs are also available as outputs, and thus have corresponding output registers with the same names.

Example 1:

{
  "logic_flow": [
    {
      "function": "Arithmetic",
      "function_params": {
        "expression": "(a + b + c & 15) % 8 ^ 3 & a ^ 10 == 4",
        "definitions": {
          "a": {
            "size": 2,
            "is_signed": false
          },
          "b": {
            "size": 1
          },
          "c": {
            "size": 3
          }
        },
        "uncomputation_method": "optimized",
        "qubit_count": 25
      }
    }
  ]
}
from classiq import ModelDesigner
from classiq.builtin_functions import Arithmetic
from classiq.interface.generator.arith.arithmetic import RegisterUserInput

params = Arithmetic(
    expression="(a + b + c & 15) % 8 ^ 3 & a ^ 10 == 4",
    definitions=dict(
        a=RegisterUserInput(size=2, is_signed=False),
        b=RegisterUserInput(size=1),
        c=RegisterUserInput(size=3),
    ),
    uncomputation_method="optimized",
    qubit_count=25,
)

model_designer = ModelDesigner()
model_designer.Arithmetic(params)
circuit = model_designer.synthesize()

This example generates a circuit which calculates the expression \(\verb|(a + b + c & 15) % 8 ^ 3 & a ^ 10 == 4|\). Each of the variables 'a','b','c' is defined to be a quantum register in the definitions. The uncomputation strategy is set to 'optimized' and the maximum number of qubits allowed for the segment is set to 25.

Output circuit:

img.png

img.png

References

[1]C. H. Bennett, “Time/space trade-offs for reversible computation,” SIAM Journal on Computing, vol. 18, no. 4, pp. 766–776, 1989.