Symbolic Simulation
pyrauli offers a powerful symbolic simulation mode that allows you to work with parameterized quantum circuits. Instead of providing fixed numerical values for parameters like gate angles or noise strengths, you can use symbolic variables. This is particularly useful for tasks like variational algorithms, gradient calculations, and sensitivity analysis, where you need to explore a function’s behavior over a range of parameter values.
The Symbolic Toolkit
The symbolic mode is built on three core classes:
SymbolicCoefficient: Represents a mathematical expression that can include variables, constants, and standard mathematical operations.SymbolicObservable: An observable whose terms have SymbolicCoefficient objects as coefficients.SymbolicCircuit: A circuit that can accept SymbolicCoefficient objects as parameters for its operations.
Working with SymbolicCoefficient
The SymbolicCoefficient is the fundamental building block. You can create one from a number or a string (which becomes a variable name).
from pyrauli import SymbolicCoefficient
# From a constant
const_coeff = SymbolicCoefficient(1.23)
# From a variable name
var_coeff = SymbolicCoefficient("theta")
print(const_coeff)
print(var_coeff)
These objects support standard mathematical operations, allowing you to build complex expressions.
a = SymbolicCoefficient("a")
b = SymbolicCoefficient("b")
# Perform standard arithmetic
expr = (a * 2 + b) / 3
print(f"Expression: {expr}")
# Use trigonometric functions
trig_expr = expr.cos()
print(f"Trigonometric Expression: {trig_expr}")
Evaluating Expressions
A key feature is the ability to substitute variables with values. There are two ways to do this:
.evaluate(): This method substitutes all variables and computes a final floating-point number. It will raise an error if any variables are left unbound.
.symbolic_evaluate(): This method substitutes only the specified variables, returning a new, potentially simpler SymbolicCoefficient.
Tip
Faster evaluations: Once you’re done building your symbolic expression, compile it into a CompiledExpression using compile(). This will speed up evaluation by orders of magnitude.
expression = SymbolicCoefficient("a") + SymbolicCoefficient("b")
# Evaluate fully by providing all variables
result = expression.evaluate({"a": 2.5, "b": 1.5})
print(f"Final scalar value: {result}")
# Partially evaluate by providing only some variables
partial_result = expression.symbolic_evaluate({"a": 2.5})
print(f"Partially evaluated expression: {partial_result}")
Simplifying Expressions
The simplified() method applies arithmetic rules (like x*1=x or x+0=x) to reduce the complexity of an expression.
x = SymbolicCoefficient("x")
# Create a redundant expression
expr = (x * 1 + 0) - (x * 0)
print(f"Original expression: {expr}")
# Simplify it
simplified_expr = expr.simplified()
print(f"Simplified expression: {simplified_expr}")
Compiling Expressions
The compile() transforms a symbolic expression into a static and highly optimized CompiledExpression.
Compiled expression are fixed and can only be evaluated, but are thousands of times faster to evaluate.
x = SymbolicCoefficient("x")
expr = (x * 1 + 0) - (x * 0)
compiled_expression = x.compile()
res = compiled_expression.evaluate({"x": 4})
#equivalent to calling .simplified() and .compile()
optimized_simplified_expression = x.optimize()
Tip
Simplifying and compiling can be done at the same time using optimize(). However, simplifying can sometimes be very long. If the goal is only to evaluate quicker, start with compiling, then simplifying using optimize.
Constructing a SymbolicObservable
A SymbolicObservable works just like a regular Observable, but its coefficients are symbolic.
from pyrauli import SymbolicObservable, SymbolicPauliTerm
# From a single Pauli string with a symbolic coefficient
obs1 = SymbolicObservable("X", "a")
print(obs1)
# From a list of Pauli strings (default coefficient is 1.0)
obs2 = SymbolicObservable(["XX", "YY"])
print(obs2)
# From a list of PauliTerms with symbolic coefficients
obs3 = SymbolicObservable([
SymbolicPauliTerm("X", "a"),
SymbolicPauliTerm("Y", "b")
])
print(obs3)
The simplify() method on an observable will simplify the symbolic coefficients of all its terms. You can also pass a dictionary of variable substitutions to this method.
from pyrauli import SymbolicObservable, SymbolicPauliTerm
# Create an observable with redundant terms
obs = SymbolicObservable(["X", "X", "Y"])
obs.merge() # ['2.000 * X', '1.000 * Y']
# Add another term with a symbolic coefficient
obs = SymbolicObservable([
SymbolicPauliTerm("X", 2.0),
SymbolicPauliTerm("Y", 1.0),
SymbolicPauliTerm("X", "a")
])
obs.merge()
print(f"Merged observable: {obs}")
# Simplify the coefficients
obs.simplify()
print(f"Simplified observable: {obs}")
# Simplify and substitute a variable
obs.simplify({"a": 3.0})
print(f"Simplified and substituted observable: {obs}")
Building and Running a SymbolicCircuit
The end-to-end workflow is straightforward. You build a SymbolicCircuit using variable names for your parameters and then run it on a SymbolicObservable. The simulation propagates the symbolic expressions through the circuit according to the rules of quantum mechanics.
circuit = SymbolicCircuit(1)
circuit.add_operation("H", 0)
circuit.add_operation("Rz", 0, "theta")
circuit.add_operation("H", 0)
The final result is a new SymbolicObservable. To get the final expectation value, you call its expectation_value() method, which returns a SymbolicCoefficient. You can then evaluate this coefficient for any set of concrete parameter values.
final_observable = circuit.run(observable)
expectation_value = final_observable.expectation_value()
# Call this before evaluating
compiled_ev = expectation_value.compile()
# Recommanded for printing
simplified_ev = expectation_value.simplified()
# Evaluate for a specific angle
value = compiled_ev.evaluate({"theta": 3.14159 / 4})
This workflow allows you to run the simulation once to get a general symbolic result and then analyze that result for many different parameter values without needing to re-run the simulation each time.
Tip
Don’t forget to compile your expressions if you are running a lot of evaluations.