Skip to content

Arithmetic Expressions

Use the Arithmetic function 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 an abstract syntax tree (AST). Then, the Classiq engine finds a computation strategy for a specified number of qubits, and compiles the desired circuit.

As opposed to classical computers, when quantum computers evaluate arithmetic expression, the calculations are reversible and are applied on all quantum states in parallel. To do so, quantum computers store all intermediate computation results in a quantum registers. 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 computation strategy determines the order in which qubits are released and reused. By employing different strategies, you can produce a variety of circuits with the same functionality. In general, longer circuits require less qubits than shorter ones.

The Classiq platform offers several computation strategies you can specify under the uncomputation_method argument:

  • The naive strategy [1] traverses the AST in topological order.
  • The optimized strategy chooses a more complicated order of computations and uncomputations, given constraints.

Besides computation strategies, you can allow input override, which may save both qubits and depth. Do it using the inputs_to_save field, defaulted to the empty set. Every input you specify in this set is available as an output of the arithmetic function. Specify inputs by name, to match keys from the definitions field. The result of the arithmetic operation is named expression_result.

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: - (two arguments)
  • Negate: - (a single argument)
  • Multiply: *
  • 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)

Syntax

Function: Arithmetic

Parameters:

  • expression: str
  • definitions: Dict[str, Union[int, float, RegisterUserInput] (see RegisterUserInput)
  • uncomputation_method: ['naive', 'optimized']
  • inputs_to_save: Set[str]
  • qubit_count: Optional[PositiveInt]
  • simplify: bool
  • 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

Determine the names of the input registers using 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 you set the allow_input_override flag to False, all inputs are also available as outputs, and thus have corresponding output registers with the same names.

Example

{
  "functions": [
    {
      "name": "main",
      "body": [
        {
          "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 Model, RegisterUserInput, synthesize
from classiq.builtin_functions import Arithmetic

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,
)
result = params.outputs[
    "expression_result"
]  # The type of the operation result quantum register

model = Model()
model.Arithmetic(params)
quantum_program = synthesize(model.get_model())

This example generates a circuit that calculates the expression \(\verb|(a + b + c & 15) % 8 ^ 3 & a ^ 10 == 4|\). Each of the variables a,b, and c is defined as a quantum register in the definitions. The uncomputation strategy is set to optimized and the maximum number of qubits allowed for the segment is 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.