Source code for uniqc.circuit_builder.parameter
"""
Symbolic parameters for parametric quantum circuits.
This module provides:
- Parameter: Named symbolic parameter for parametric gates
- Parameters: Array of named parameters with indexing support
These classes enable symbolic expressions for gate parameters that can be
bound to concrete values at execution time.
Example usage::
theta = Parameter("theta")
phi = Parameter("phi")
# Arithmetic operations create symbolic expressions
expr = theta + phi / 2
# Bind values and evaluate
theta.bind(1.0)
phi.bind(2.0)
result = expr.evalf() # 2.0
"""
from __future__ import annotations
from collections.abc import Iterator
from typing import TYPE_CHECKING
import sympy as sp
if TYPE_CHECKING:
pass
__all__ = ["Parameter", "Parameters"]
[docs]
class Parameter:
"""Named symbolic parameter for parametric quantum circuits.
Parameter supports arithmetic operations that create symbolic expressions
using sympy. The parameter can be bound to a concrete value or evaluated
with a provided values dictionary.
Attributes:
name: Parameter name
symbol: Underlying sympy Symbol
Example:
>>> theta = Parameter("theta")
>>> phi = Parameter("phi")
>>> expr = theta * 2 + phi
>>> # Evaluate with concrete values
>>> float(expr.subs({theta.symbol: 1.0, phi.symbol: 0.5}))
2.5
"""
def __init__(self, name: str) -> None:
"""Initialize a named parameter.
Args:
name: Parameter name (must be unique within a circuit)
"""
self._name = name
self._symbol = sp.Symbol(name)
self._bound_value: float | None = None
@property
def name(self) -> str:
return self._name
@property
def symbol(self) -> sp.Symbol:
return self._symbol
[docs]
def bind(self, value: float) -> None:
"""Bind a concrete value to this parameter.
Args:
value: The numeric value to bind
"""
self._bound_value = float(value)
[docs]
def evaluate(self, values: dict[str, float] | None = None) -> float:
"""Evaluate the parameter.
If the parameter is bound, returns the bound value.
Otherwise, looks up the value in the provided dictionary.
Args:
values: Optional dictionary mapping parameter names to values
Returns:
The evaluated numeric value
Raises:
ValueError: If parameter is not bound and not in values dict
"""
if self._bound_value is not None:
return self._bound_value
if values is not None and self._name in values:
return values[self._name]
raise ValueError(f"Parameter '{self._name}' is not bound and no value provided")
@property
def is_bound(self) -> bool:
"""Check if the parameter has a bound value."""
return self._bound_value is not None
def __add__(self, other: Parameter | float | int) -> sp.Expr:
return self._symbol + (other._symbol if isinstance(other, Parameter) else other)
def __radd__(self, other: float | int) -> sp.Expr:
return other + self._symbol
def __sub__(self, other: Parameter | float | int) -> sp.Expr:
return self._symbol - (other._symbol if isinstance(other, Parameter) else other)
def __rsub__(self, other: float | int) -> sp.Expr:
return other - self._symbol
def __mul__(self, other: Parameter | float | int) -> sp.Expr:
return self._symbol * (other._symbol if isinstance(other, Parameter) else other)
def __rmul__(self, other: float | int) -> sp.Expr:
return other * self._symbol
def __truediv__(self, other: Parameter | float | int) -> sp.Expr:
return self._symbol / (other._symbol if isinstance(other, Parameter) else other)
def __rtruediv__(self, other: float | int) -> sp.Expr:
return other / self._symbol
def __neg__(self) -> sp.Expr:
return -self._symbol
def __repr__(self) -> str:
bound_str = f"={self._bound_value}" if self._bound_value is not None else ""
return f"Parameter({self._name!r}{bound_str})"
def __hash__(self) -> int:
return hash(self._name)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Parameter):
return NotImplemented
return self._name == other._name
[docs]
class Parameters:
"""Array of named parameters with indexing support.
Creates multiple Parameter objects with names "{name}_{index}".
Attributes:
name: Base name for the parameter array
Example:
>>> alphas = Parameters("alpha", size=4)
>>> alphas[0]
Parameter('alpha_0')
>>> alphas.names
['alpha_0', 'alpha_1', 'alpha_2', 'alpha_3']
>>> alphas.bind([0.1, 0.2, 0.3, 0.4])
>>> alphas[0].evaluate()
0.1
"""
def __init__(self, name: str, size: int) -> None:
"""Initialize a parameter array.
Args:
name: Base name for parameters
size: Number of parameters in the array
Raises:
ValueError: If size is not positive
"""
if size <= 0:
raise ValueError(f"Parameters size must be positive, got {size}")
self._name = name
self._params: list[Parameter] = [Parameter(f"{name}_{i}") for i in range(size)]
@property
def name(self) -> str:
return self._name
@property
def names(self) -> list[str]:
"""Return list of all parameter names."""
return [p.name for p in self._params]
@property
def symbols(self) -> list[sp.Symbol]:
"""Return list of all sympy symbols."""
return [p.symbol for p in self._params]
def __len__(self) -> int:
return len(self._params)
def __getitem__(self, index: int) -> Parameter:
return self._params[index]
def __iter__(self) -> Iterator[Parameter]:
return iter(self._params)
[docs]
def bind(self, values: list[float]) -> None:
"""Bind values to all parameters.
Args:
values: List of values (must match array size)
Raises:
ValueError: If values length doesn't match array size
"""
if len(values) != len(self._params):
raise ValueError(f"Values length {len(values)} doesn't match Parameters size {len(self._params)}")
for param, value in zip(self._params, values, strict=True):
param.bind(value)
[docs]
def evaluate(self, values: list[float] | None = None) -> list[float]:
"""Evaluate all parameters.
Args:
values: Optional list of values to use (must match array size)
Returns:
List of evaluated values
"""
if values is not None:
self.bind(values)
return [p.evaluate() for p in self._params]
def __repr__(self) -> str:
return f"Parameters({self._name!r}, size={len(self._params)})"