Python SDK
Classiq SDK.
analyzer
special
¶
analyzer
¶
Analyzer module, implementing facilities for analyzing circuits using Classiq platform.
Analyzer (AnalyzerUtilities)
¶
Analyzer is the wrapper object for all analysis capabilities.
Source code in classiq/analyzer/analyzer.py
class Analyzer(AnalyzerUtilities):
"""Analyzer is the wrapper object for all analysis capabilities."""
def __init__(self, circuit: generator_result.QuantumProgram) -> None:
"""Init self.
Args:
circuit (): The circuit to be analyzed.
"""
if circuit.qasm is None:
raise ClassiqAnalyzerError(
"Analysis requires a circuit with valid QASM code"
)
params: analysis_params.AnalysisParams = analysis_params.AnalysisParams(
qasm=circuit.qasm
)
super().__init__(
params=params,
circuit=circuit,
available_devices=dict(),
hardware_graphs=dict(),
)
self.hardware_comparison_table: Optional[go.Figure] = None
self.transpilation_params = analysis_params.AnalysisHardwareTranspilationParams(
hardware_data=self.circuit.hardware_data,
random_seed=self.circuit.model.execution_preferences.random_seed,
transpilation_option=self.circuit.model.execution_preferences.transpile_to_hardware,
)
def analyzer_app(self) -> None:
"""Opens the analyzer app with synthesis interactive results.
Returns:
None.
"""
result = async_utils.run(ApiWrapper.call_analyzer_app(self.circuit))
webbrowser.open_new_tab(
urljoin(
client_ide_base_url(),
circuit_page_uri(
circuit_id=result.id, circuit_version=self.circuit.version
),
)
)
def get_available_devices(
self, providers: Optional[List[ProviderNameEnum]] = None
) -> Dict[ProviderNameEnum, List[DeviceName]]:
"""Deprecated. Use get_all_hardware_devices instead.
Returns dict of the available devices by the providers. only devices
with sufficient number of qubits are returns
Args: providers (): List of providers (string or `AnalyzerProviderVendor`).
if None, the table include all the available hardware.
Returns:
available devices (): dict of the available devices (Dict[str,List[str]]).
"""
if providers is None:
providers = list(AnalyzerProviderVendor)
async_utils.run(self._request_available_devices_async(providers=providers))
return {
provider: self._filter_devices_by_qubits_count(provider)
for provider in providers
}
def plot_hardware_connectivity(
self,
provider: Optional[ProviderNameEnum] = None,
device: Optional[DeviceName] = None,
) -> VBox:
"""plot the hardware_connectivity graph. It is required to required install the
analyzer_sdk extra.
Args:
provider (): provider name (optional - string or `AnalyzerProviderVendor`).
device (): device name (optional - string).
Returns:
hardware_connectivity_graph (): interactive graph.
"""
self._validate_analyzer_extra()
interactive_hardware = InteractiveHardware(
circuit=self.circuit,
params=self._params,
available_devices=self.available_devices,
hardware_graphs=self.hardware_graphs,
)
async_utils.run(interactive_hardware.enable_interactivity_async())
if provider is not None:
interactive_hardware.providers_combobox.value = provider
if device is not None:
interactive_hardware.devices_combobox.value = device
return interactive_hardware.show_interactive_graph()
def get_hardware_comparison_table(
self,
providers: Optional[Sequence[Union[str, AnalyzerProviderVendor]]] = None,
devices: Optional[List[str]] = None,
) -> None:
"""create a comparison table between the transpiled circuits result on different hardware.
The comparison table included the depth, multi qubit gates count,and total gates count of the circuits.
Args: providers (): List of providers (string or `AnalyzerProviderVendor`). if None, the table include all
the available hardware.
devices (): List of devices (string). if None, the table include all the available devices of the selected
providers.
Returns: None.
"""
if providers is None:
providers = list(AnalyzerProviderVendor)
params = analysis_params.AnalysisHardwareListParams(
qasm=self._params.qasm,
providers=providers,
devices=devices,
transpilation_params=self.transpilation_params,
)
result = async_utils.run(ApiWrapper.call_table_graphs_task(params=params))
self.hardware_comparison_table = go.Figure(json.loads(result.details))
def plot_hardware_comparison_table(
self,
providers: Optional[List[Union[str, AnalyzerProviderVendor]]] = None,
devices: Optional[List[str]] = None,
) -> None:
"""plot the comparison table. if it has not been created it, it first creates the table using all the
available hardware.
Returns:
None.
"""
self._hardware_comparison_condition(providers=providers, devices=devices)
self.hardware_comparison_table.show() # type: ignore[union-attr]
def _hardware_comparison_condition(
self,
providers: Optional[Sequence[Union[str, AnalyzerProviderVendor]]] = None,
devices: Optional[List[str]] = None,
) -> None:
if (
providers is not None
or devices is not None
or self.hardware_comparison_table is None
):
self.get_hardware_comparison_table(providers=providers, devices=devices)
@staticmethod
def _open_route(path: str) -> None:
backend_uri = client.client().get_backend_uri()
webbrowser.open_new_tab(f"{backend_uri}{path}")
@staticmethod
def _validate_analyzer_extra() -> None:
if find_ipywidgets is None:
raise ClassiqAnalyzerError(
"To use this method, please install the `analyzer sdk`. Run the \
following line: - pip install classiq[analyzer_sdk]"
)
__init__(self, circuit)
special
¶
Init self.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
circuit |
The circuit to be analyzed. |
required |
Source code in classiq/analyzer/analyzer.py
def __init__(self, circuit: generator_result.QuantumProgram) -> None:
"""Init self.
Args:
circuit (): The circuit to be analyzed.
"""
if circuit.qasm is None:
raise ClassiqAnalyzerError(
"Analysis requires a circuit with valid QASM code"
)
params: analysis_params.AnalysisParams = analysis_params.AnalysisParams(
qasm=circuit.qasm
)
super().__init__(
params=params,
circuit=circuit,
available_devices=dict(),
hardware_graphs=dict(),
)
self.hardware_comparison_table: Optional[go.Figure] = None
self.transpilation_params = analysis_params.AnalysisHardwareTranspilationParams(
hardware_data=self.circuit.hardware_data,
random_seed=self.circuit.model.execution_preferences.random_seed,
transpilation_option=self.circuit.model.execution_preferences.transpile_to_hardware,
)
analyzer_app(self)
¶
Opens the analyzer app with synthesis interactive results.
Returns:
Type | Description |
---|---|
None |
None. |
Source code in classiq/analyzer/analyzer.py
def analyzer_app(self) -> None:
"""Opens the analyzer app with synthesis interactive results.
Returns:
None.
"""
result = async_utils.run(ApiWrapper.call_analyzer_app(self.circuit))
webbrowser.open_new_tab(
urljoin(
client_ide_base_url(),
circuit_page_uri(
circuit_id=result.id, circuit_version=self.circuit.version
),
)
)
get_available_devices(self, providers=None)
¶
Deprecated. Use get_all_hardware_devices instead.
Returns dict of the available devices by the providers. only devices with sufficient number of qubits are returns
Args: providers (): List of providers (string or AnalyzerProviderVendor
).
if None, the table include all the available hardware.
Returns:
Type | Description |
---|---|
available devices () |
dict of the available devices (Dict[str,List[str]]). |
Source code in classiq/analyzer/analyzer.py
def get_available_devices(
self, providers: Optional[List[ProviderNameEnum]] = None
) -> Dict[ProviderNameEnum, List[DeviceName]]:
"""Deprecated. Use get_all_hardware_devices instead.
Returns dict of the available devices by the providers. only devices
with sufficient number of qubits are returns
Args: providers (): List of providers (string or `AnalyzerProviderVendor`).
if None, the table include all the available hardware.
Returns:
available devices (): dict of the available devices (Dict[str,List[str]]).
"""
if providers is None:
providers = list(AnalyzerProviderVendor)
async_utils.run(self._request_available_devices_async(providers=providers))
return {
provider: self._filter_devices_by_qubits_count(provider)
for provider in providers
}
get_hardware_comparison_table(self, providers=None, devices=None)
¶
create a comparison table between the transpiled circuits result on different hardware. The comparison table included the depth, multi qubit gates count,and total gates count of the circuits.
Args: providers (): List of providers (string or AnalyzerProviderVendor
). if None, the table include all
the available hardware.
devices (): List of devices (string). if None, the table include all the available devices of the selected
providers.
Returns: None.
Source code in classiq/analyzer/analyzer.py
def get_hardware_comparison_table(
self,
providers: Optional[Sequence[Union[str, AnalyzerProviderVendor]]] = None,
devices: Optional[List[str]] = None,
) -> None:
"""create a comparison table between the transpiled circuits result on different hardware.
The comparison table included the depth, multi qubit gates count,and total gates count of the circuits.
Args: providers (): List of providers (string or `AnalyzerProviderVendor`). if None, the table include all
the available hardware.
devices (): List of devices (string). if None, the table include all the available devices of the selected
providers.
Returns: None.
"""
if providers is None:
providers = list(AnalyzerProviderVendor)
params = analysis_params.AnalysisHardwareListParams(
qasm=self._params.qasm,
providers=providers,
devices=devices,
transpilation_params=self.transpilation_params,
)
result = async_utils.run(ApiWrapper.call_table_graphs_task(params=params))
self.hardware_comparison_table = go.Figure(json.loads(result.details))
plot_hardware_comparison_table(self, providers=None, devices=None)
¶
plot the comparison table. if it has not been created it, it first creates the table using all the available hardware.
Returns:
Type | Description |
---|---|
None |
None. |
Source code in classiq/analyzer/analyzer.py
def plot_hardware_comparison_table(
self,
providers: Optional[List[Union[str, AnalyzerProviderVendor]]] = None,
devices: Optional[List[str]] = None,
) -> None:
"""plot the comparison table. if it has not been created it, it first creates the table using all the
available hardware.
Returns:
None.
"""
self._hardware_comparison_condition(providers=providers, devices=devices)
self.hardware_comparison_table.show() # type: ignore[union-attr]
plot_hardware_connectivity(self, provider=None, device=None)
¶
plot the hardware_connectivity graph. It is required to required install the analyzer_sdk extra.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
provider |
provider name (optional - string or |
None |
|
device |
device name (optional - string). |
None |
Returns:
Type | Description |
---|---|
hardware_connectivity_graph () |
interactive graph. |
Source code in classiq/analyzer/analyzer.py
def plot_hardware_connectivity(
self,
provider: Optional[ProviderNameEnum] = None,
device: Optional[DeviceName] = None,
) -> VBox:
"""plot the hardware_connectivity graph. It is required to required install the
analyzer_sdk extra.
Args:
provider (): provider name (optional - string or `AnalyzerProviderVendor`).
device (): device name (optional - string).
Returns:
hardware_connectivity_graph (): interactive graph.
"""
self._validate_analyzer_extra()
interactive_hardware = InteractiveHardware(
circuit=self.circuit,
params=self._params,
available_devices=self.available_devices,
hardware_graphs=self.hardware_graphs,
)
async_utils.run(interactive_hardware.enable_interactivity_async())
if provider is not None:
interactive_hardware.providers_combobox.value = provider
if device is not None:
interactive_hardware.devices_combobox.value = device
return interactive_hardware.show_interactive_graph()
rb
¶
RBAnalysis
¶
Source code in classiq/analyzer/rb.py
class RBAnalysis:
def __init__(self, experiments_data: List[AnalysisRBParams]) -> None:
"""Init self.
Args:
experiments_data: List of results from varius RB experiments.
"""
self.experiments_data = experiments_data
self._total_results: pd.DataFrame = pd.DataFrame()
async def _get_multiple_hardware_results_async(self) -> Dict[str, RbResults]:
total_result: Dict[str, RbResults] = {}
for batch in self.experiments_data:
if len(batch.num_clifford) < 5:
raise ClassiqAnalyzerError(
f"An experiment mush contain at least five sequences,"
f" this sequence is {len(batch.num_clifford)}"
)
rb_result = await ApiWrapper.call_rb_analysis_task(batch)
total_result[batch.hardware] = rb_result
return total_result
@staticmethod
def _get_df_indices(results: Dict[str, RbResults]) -> List[str]:
temp_res = results.copy()
_, rb_result_keys = temp_res.popitem()
return list(rb_result_keys.__dict__.keys())
async def show_multiple_hardware_data_async(self) -> pd.DataFrame:
"""Run the RB analysis.
Returns:
The RB result.
"""
results = await self._get_multiple_hardware_results_async()
indices = RBAnalysis._get_df_indices(results)
result_df = pd.DataFrame(index=indices)
for hardware, result in results.items():
result_df[hardware] = result.__dict__.values()
self._total_results = result_df
return result_df
def plot_multiple_hardware_results(self) -> go.Figure:
"""Plot Bar graph of the results.
Returns:
None.
"""
df = self._total_results.loc[["mean_fidelity", "average_error"]].transpose()
hardware = list(df.index)
params = list(df.columns)
data = [
go.Bar(name=param, x=hardware, y=df[param].values * 100) for param in params
]
fig = go.Figure(data).update_layout(
title="RB hardware comparison",
barmode="group",
yaxis=dict(title="Fidelity in %"),
xaxis=dict(title="Hardware"),
)
return fig
__init__(self, experiments_data)
special
¶
Init self.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
experiments_data |
List[classiq.interface.analyzer.analysis_params.AnalysisRBParams] |
List of results from varius RB experiments. |
required |
Source code in classiq/analyzer/rb.py
def __init__(self, experiments_data: List[AnalysisRBParams]) -> None:
"""Init self.
Args:
experiments_data: List of results from varius RB experiments.
"""
self.experiments_data = experiments_data
self._total_results: pd.DataFrame = pd.DataFrame()
plot_multiple_hardware_results(self)
¶
Plot Bar graph of the results.
Returns:
Type | Description |
---|---|
Figure |
None. |
Source code in classiq/analyzer/rb.py
def plot_multiple_hardware_results(self) -> go.Figure:
"""Plot Bar graph of the results.
Returns:
None.
"""
df = self._total_results.loc[["mean_fidelity", "average_error"]].transpose()
hardware = list(df.index)
params = list(df.columns)
data = [
go.Bar(name=param, x=hardware, y=df[param].values * 100) for param in params
]
fig = go.Figure(data).update_layout(
title="RB hardware comparison",
barmode="group",
yaxis=dict(title="Fidelity in %"),
xaxis=dict(title="Hardware"),
)
return fig
show_multiple_hardware_data_async(self)
async
¶
Run the RB analysis.
Returns:
Type | Description |
---|---|
DataFrame |
The RB result. |
Source code in classiq/analyzer/rb.py
async def show_multiple_hardware_data_async(self) -> pd.DataFrame:
"""Run the RB analysis.
Returns:
The RB result.
"""
results = await self._get_multiple_hardware_results_async()
indices = RBAnalysis._get_df_indices(results)
result_df = pd.DataFrame(index=indices)
for hardware, result in results.items():
result_df[hardware] = result.__dict__.values()
self._total_results = result_df
return result_df
applications
special
¶
chemistry
special
¶
ansatz_parameters
¶
HEAParameters
dataclass
¶
HEAParameters(reps: int, num_qubits: int, connectivity_map: List[Tuple[int, int]], one_qubit_gates: List[str], two_qubit_gates: List[str])
Source code in classiq/applications/chemistry/ansatz_parameters.py
@dataclasses.dataclass
class HEAParameters:
reps: int
num_qubits: int
connectivity_map: List[Tuple[int, int]]
one_qubit_gates: List[str]
two_qubit_gates: List[str]
HVAParameters
dataclass
¶
HVAParameters(reps: int)
Source code in classiq/applications/chemistry/ansatz_parameters.py
@dataclasses.dataclass
class HVAParameters:
reps: int
UCCParameters
dataclass
¶
UCCParameters(excitations: List[int] =
Source code in classiq/applications/chemistry/ansatz_parameters.py
@dataclasses.dataclass
class UCCParameters:
excitations: EXCITATIONS_TYPE_EXACT = dataclasses.field(
default_factory=default_excitation_factory
)
chemistry_execution_parameters
¶
ChemistryExecutionParameters
dataclass
¶
ChemistryExecutionParameters(optimizer: classiq.interface.executor.optimizer_preferences.OptimizerType, max_iteration: int, initial_point: Optional[numpy.ndarray] = None, tolerance: float = 0.0, step_size: float = 0.0, skip_compute_variance: bool = False)
Source code in classiq/applications/chemistry/chemistry_execution_parameters.py
@dataclasses.dataclass
class ChemistryExecutionParameters:
optimizer: OptimizerType
max_iteration: int
initial_point: Optional[np.ndarray] = dataclasses.field(default=None)
tolerance: float = dataclasses.field(default=0.0)
step_size: float = dataclasses.field(default=0.0)
skip_compute_variance: bool = dataclasses.field(default=False)
combinatorial_helpers
special
¶
encoding_mapping
¶
VarExpressionMapping
dataclass
¶
VarExpressionMapping(var: pyomo.core.base.var._GeneralVarData, expr: pyomo.core.base.expression.Expression, encodings_vars: List[pyomo.core.base.var._GeneralVarData] =
Source code in classiq/applications/combinatorial_helpers/encoding_mapping.py
@dataclass
class VarExpressionMapping:
var: _GeneralVarData
expr: pyo.Expression
encodings_vars: List[_GeneralVarData] = field(default_factory=list)
pauli_helpers
special
¶
pauli_sparsing
¶
SparsePauliOp
¶
Source code in classiq/applications/combinatorial_helpers/pauli_helpers/pauli_sparsing.py
class SparsePauliOp:
def __init__(self, paulis: Any, coeffs: Any) -> None:
assert len(paulis) == len(
coeffs
), "Paulis and coefficients lists must have the same length."
self.paulis = np.array(paulis)
self.coeffs = np.array(coeffs, dtype=complex)
def __str__(self) -> str:
terms = [f"{coef}*{pauli}" for coef, pauli in zip(self.coeffs, self.paulis)]
return " + ".join(terms)
def __add__(self, other: "SparsePauliOp") -> "SparsePauliOp":
"""Add two SparsePauliOp objects."""
if not isinstance(other, SparsePauliOp):
raise ValueError("Can only add SparsePauliOp objects.")
new_paulis = np.concatenate([self.paulis, other.paulis])
new_coeffs = np.concatenate([self.coeffs, other.coeffs])
return SparsePauliOp(new_paulis, new_coeffs)
def __mul__(self, other: Union[int, float, complex]) -> "SparsePauliOp":
"""Scalar multiplication of a SparsePauliOp."""
if not isinstance(other, (int, float, complex)):
raise ValueError("Can only multiply by scalar values.")
new_coeffs = self.coeffs * other
return SparsePauliOp(self.paulis, new_coeffs)
__add__(self, other)
special
¶Add two SparsePauliOp objects.
Source code in classiq/applications/combinatorial_helpers/pauli_helpers/pauli_sparsing.py
def __add__(self, other: "SparsePauliOp") -> "SparsePauliOp":
"""Add two SparsePauliOp objects."""
if not isinstance(other, SparsePauliOp):
raise ValueError("Can only add SparsePauliOp objects.")
new_paulis = np.concatenate([self.paulis, other.paulis])
new_coeffs = np.concatenate([self.coeffs, other.coeffs])
return SparsePauliOp(new_paulis, new_coeffs)
__mul__(self, other)
special
¶Scalar multiplication of a SparsePauliOp.
Source code in classiq/applications/combinatorial_helpers/pauli_helpers/pauli_sparsing.py
def __mul__(self, other: Union[int, float, complex]) -> "SparsePauliOp":
"""Scalar multiplication of a SparsePauliOp."""
if not isinstance(other, (int, float, complex)):
raise ValueError("Can only multiply by scalar values.")
new_coeffs = self.coeffs * other
return SparsePauliOp(self.paulis, new_coeffs)
pyomo_utils
¶
CombinatorialOptimizationStructDeclaration (StructDeclaration)
pydantic-model
¶
Source code in classiq/applications/combinatorial_helpers/pyomo_utils.py
class CombinatorialOptimizationStructDeclaration(StructDeclaration):
variable_lower_bound: int = pydantic.Field(default=0)
variable_upper_bound: int = pydantic.Field(default=1)
constraints: List[Expression] = pydantic.Field(
default_factory=list, description="List of constraint expressions"
)
objective_type: ObjectiveType = pydantic.Field(
description="Specify whether the optimization problem is Min or Max"
)
objective_function: Expression = pydantic.Field(
description="The expression to optimize, according to the objective type"
)
constraints: List[classiq.interface.generator.expressions.expression.Expression]
pydantic-field
¶
List of constraint expressions
objective_function: Expression
pydantic-field
required
¶
The expression to optimize, according to the objective type
objective_type: ObjectiveType
pydantic-field
required
¶
Specify whether the optimization problem is Min or Max
combinatorial_optimization
special
¶
combinatorial_optimization_config
¶
OptimizerConfig
dataclass
¶
OptimizerConfig(opt_type: classiq.interface.executor.optimizer_preferences.OptimizerType =
Source code in classiq/applications/combinatorial_optimization/combinatorial_optimization_config.py
@dataclass
class OptimizerConfig:
opt_type: OptimizerType = OptimizerType.COBYLA
max_iteration: Optional[int] = None
tolerance: float = 0.0
step_size: float = 0.0
skip_compute_variance: bool = False
cost_type: CostType = CostType.CVAR
alpha_cvar: float = 1.0
initial_point: Optional[List[float]] = dataclasses.field(default=None)
QAOAConfig
dataclass
¶
QAOAConfig(num_layers: int = 2, penalty_energy: float = 2.0)
Source code in classiq/applications/combinatorial_optimization/combinatorial_optimization_config.py
@dataclass
class QAOAConfig:
num_layers: int = 2
penalty_energy: float = 2.0
qnn
special
¶
datasets
special
¶
datasets_utils
¶
all_bits_to_one(n)
¶
Return an integer of length n
bits, where all the bits are 1
Source code in classiq/applications/qnn/datasets/datasets_utils.py
def all_bits_to_one(n: int) -> int:
"""
Return an integer of length `n` bits, where all the bits are `1`
"""
return (2**n) - 1
all_bits_to_zero(n)
¶
Return an integer of length n
bits, where all the bits are 0
Source code in classiq/applications/qnn/datasets/datasets_utils.py
def all_bits_to_zero(n: int) -> int:
"""
Return an integer of length `n` bits, where all the bits are `0`
"""
return 0
state_to_label(pure_state)
¶
input: a Tensor
of binary numbers (0 or 1) - the return value of a measurement
output: probability (from that measurement) of measuring 0
(in other words,
|0> translates to 100% chance for measuring |0> ==> return value is 1.0
|1> translates to 0% chance for measuring |0> ==> return value is 0.0
)
Source code in classiq/applications/qnn/datasets/datasets_utils.py
def state_to_label(pure_state: Tensor) -> Tensor:
"""
input: a `Tensor` of binary numbers (0 or 1) - the return value of a measurement
output: probability (from that measurement) of measuring 0
(in other words,
|0> translates to 100% chance for measuring |0> ==> return value is 1.0
|1> translates to 0% chance for measuring |0> ==> return value is 0.0
)
"""
# |0> means 100% chance to get |0> ==> 100% == 1.0
# |1> means 0% chance to get |0> ==> 0% == 0.0
# This line basically does `1 - bool(pure_state)`
return 1 - pure_state.bool().int()
state_to_weights(pure_state)
¶
input: a Tensor
of binary numbers (0 or 1)
output: the required angle of rotation for Rx
(in other words, |0> translates to no rotation, and |1> translates to pi
)
Source code in classiq/applications/qnn/datasets/datasets_utils.py
def state_to_weights(pure_state: Tensor) -> Tensor:
"""
input: a `Tensor` of binary numbers (0 or 1)
output: the required angle of rotation for `Rx`
(in other words, |0> translates to no rotation, and |1> translates to `pi`)
"""
# |0> requires a rotation by 0
# |1> requires a rotation by pi
return pure_state.bool().int() * np.pi
qlayer
¶
QLayer (Module)
¶
Source code in classiq/applications/qnn/qlayer.py
class QLayer(nn.Module):
def __init__(
self,
quantum_program: SerializedQuantumProgram,
execute: ExecuteFunction,
post_process: PostProcessFunction,
# Optional parameters:
head_start: Union[float, Tensor, None] = None,
# Experimental parameters:
calc_num_out_features: CalcNumOutFeatures = calc_num_out_features_single_output,
) -> None:
circuit = Circuit.parse_raw(quantum_program)
validate_circuit(circuit)
super().__init__()
self._execute = execute
self._post_process = post_process
self._head_start = head_start
self.quantum_program = quantum_program
weights, _ = extract_parameters(circuit)
self.in_features: int = len(weights)
self.out_features: int = calc_num_out_features(quantum_program)
self._initialize_parameters()
def _initialize_parameters(self) -> None:
shape: Tuple[int, ...] = (
(self.out_features, self.in_features)
if self.out_features > 1
else (self.in_features,)
)
if self._head_start is None:
value = torch.rand(shape)
elif isinstance(self._head_start, (float, int)):
value = torch.zeros(shape) + self._head_start
elif isinstance(self._head_start, Tensor):
value = self._head_start.clone()
else:
raise ClassiqQNNError(
f"Unsupported feature - head_start of type {type(self._head_start)}"
)
self.weight = Parameter(value)
def forward(self, x: Tensor) -> Tensor:
return QLayerFunction.apply( # type: ignore[no-untyped-call]
x, self.weight, self.quantum_program, self._execute, self._post_process
)
forward(self, x)
¶
Define the computation performed at every call.
Should be overridden by all subclasses.
.. note::
Although the recipe for forward pass needs to be defined within
this function, one should call the :class:Module
instance afterwards
instead of this since the former takes care of running the
registered hooks while the latter silently ignores them.
Source code in classiq/applications/qnn/qlayer.py
def forward(self, x: Tensor) -> Tensor:
return QLayerFunction.apply( # type: ignore[no-untyped-call]
x, self.weight, self.quantum_program, self._execute, self._post_process
)
QLayerFunction (Function)
¶
Source code in classiq/applications/qnn/qlayer.py
class QLayerFunction(torch.autograd.Function):
@staticmethod
def forward( # type: ignore[override]
ctx: Any,
inputs: Tensor,
weights: Tensor,
quantum_program: SerializedQuantumProgram,
execute: ExecuteFunction,
post_process: PostProcessFunction,
) -> Tensor:
"""
This function receives:
inputs: a 2D Tensor of floats - (batch_size, in_features)
weights: a 2D Tensor of floats - (out_features, num_weights)
circuit: a `GeneratedCircuit` object
execute: a function taking a `GeneratedCircuit` and `MultipleArguments`
and returning `MultipleExecutionDetails`
post_process: a function taking a single `ExecutionDetails`
and returning a `Tensor`
"""
circuit = Circuit.parse_raw(quantum_program)
validate_circuit(circuit)
# save for backward
ctx.save_for_backward(inputs, weights)
ctx.quantum_program = quantum_program
ctx.execute = execute
ctx.post_process = post_process
ctx.quantum_gradient = SimpleQuantumGradient(
quantum_program, execute, post_process
)
ctx.batch_size, ctx.num_in_features = inputs.shape
if is_single_layer_circuit(weights):
ctx.num_weights = weights.shape
else:
ctx.num_out_features, ctx.num_weights = weights.shape
# Todo: avoid computing `_get_extracted_parameters` on every `forward`
extracted_parameters = extract_parameters(circuit)
# Todo: avoid defining `convert_tensors_to_arguments` on every `forward`
def convert_tensors_to_arguments(
inputs_: Tensor, weights_: Tensor
) -> MultipleArguments:
arguments = map_parameters(
extracted_parameters,
inputs_,
weights_,
)
return (arguments,)
return iter_inputs_weights(
inputs,
weights,
convert_tensors_to_arguments,
functools.partial(execute, quantum_program),
post_process,
)
@staticmethod
def backward( # type: ignore[override]
ctx: Any, grad_output: Tensor
) -> Tuple[Optional[Tensor], Optional[Tensor], None, None, None]:
"""
grad_output: Tensor
is of shape (ctx.batch_size, ctx.num_out_features)
"""
inputs, weights = ctx.saved_tensors
grad_weights = grad_inputs = None
grad_circuit = grad_execute = grad_post_process = None
is_single_layer = is_single_layer_circuit(weights)
if ctx.needs_input_grad[1]:
grad_weights = ctx.quantum_gradient.gradient_weights(inputs, weights)
grad_weights = einsum_weigths(grad_output, grad_weights, is_single_layer)
if ctx.needs_input_grad[0]:
grad_inputs = ctx.quantum_gradient.gradient_inputs(inputs, weights)
grad_inputs = einsum_inputs(grad_output, grad_inputs, is_single_layer)
if any(ctx.needs_input_grad[i] for i in (2, 3, 4)):
raise ClassiqTorchError(
f"Grad required for unknown type: {ctx.needs_input_grad}"
)
return grad_inputs, grad_weights, grad_circuit, grad_execute, grad_post_process
backward(ctx, grad_output)
staticmethod
¶
Tensor
is of shape (ctx.batch_size, ctx.num_out_features)
Source code in classiq/applications/qnn/qlayer.py
@staticmethod
def backward( # type: ignore[override]
ctx: Any, grad_output: Tensor
) -> Tuple[Optional[Tensor], Optional[Tensor], None, None, None]:
"""
grad_output: Tensor
is of shape (ctx.batch_size, ctx.num_out_features)
"""
inputs, weights = ctx.saved_tensors
grad_weights = grad_inputs = None
grad_circuit = grad_execute = grad_post_process = None
is_single_layer = is_single_layer_circuit(weights)
if ctx.needs_input_grad[1]:
grad_weights = ctx.quantum_gradient.gradient_weights(inputs, weights)
grad_weights = einsum_weigths(grad_output, grad_weights, is_single_layer)
if ctx.needs_input_grad[0]:
grad_inputs = ctx.quantum_gradient.gradient_inputs(inputs, weights)
grad_inputs = einsum_inputs(grad_output, grad_inputs, is_single_layer)
if any(ctx.needs_input_grad[i] for i in (2, 3, 4)):
raise ClassiqTorchError(
f"Grad required for unknown type: {ctx.needs_input_grad}"
)
return grad_inputs, grad_weights, grad_circuit, grad_execute, grad_post_process
forward(ctx, inputs, weights, quantum_program, execute, post_process)
staticmethod
¶
This function receives:
inputs: a 2D Tensor of floats - (batch_size, in_features)
weights: a 2D Tensor of floats - (out_features, num_weights)
circuit: a GeneratedCircuit
object
!!! execute "a function taking a GeneratedCircuit
and MultipleArguments
"
and returning MultipleExecutionDetails
!!! post_process "a function taking a single ExecutionDetails
"
and returning a Tensor
Source code in classiq/applications/qnn/qlayer.py
@staticmethod
def forward( # type: ignore[override]
ctx: Any,
inputs: Tensor,
weights: Tensor,
quantum_program: SerializedQuantumProgram,
execute: ExecuteFunction,
post_process: PostProcessFunction,
) -> Tensor:
"""
This function receives:
inputs: a 2D Tensor of floats - (batch_size, in_features)
weights: a 2D Tensor of floats - (out_features, num_weights)
circuit: a `GeneratedCircuit` object
execute: a function taking a `GeneratedCircuit` and `MultipleArguments`
and returning `MultipleExecutionDetails`
post_process: a function taking a single `ExecutionDetails`
and returning a `Tensor`
"""
circuit = Circuit.parse_raw(quantum_program)
validate_circuit(circuit)
# save for backward
ctx.save_for_backward(inputs, weights)
ctx.quantum_program = quantum_program
ctx.execute = execute
ctx.post_process = post_process
ctx.quantum_gradient = SimpleQuantumGradient(
quantum_program, execute, post_process
)
ctx.batch_size, ctx.num_in_features = inputs.shape
if is_single_layer_circuit(weights):
ctx.num_weights = weights.shape
else:
ctx.num_out_features, ctx.num_weights = weights.shape
# Todo: avoid computing `_get_extracted_parameters` on every `forward`
extracted_parameters = extract_parameters(circuit)
# Todo: avoid defining `convert_tensors_to_arguments` on every `forward`
def convert_tensors_to_arguments(
inputs_: Tensor, weights_: Tensor
) -> MultipleArguments:
arguments = map_parameters(
extracted_parameters,
inputs_,
weights_,
)
return (arguments,)
return iter_inputs_weights(
inputs,
weights,
convert_tensors_to_arguments,
functools.partial(execute, quantum_program),
post_process,
)
execution
special
¶
all_hardware_devices
¶
get_all_hardware_devices()
¶
Returns a list of all hardware devices known to Classiq.
Source code in classiq/execution/all_hardware_devices.py
def get_all_hardware_devices() -> List[HardwareInformation]:
"""
Returns a list of all hardware devices known to Classiq.
"""
return async_utils.run(ApiWrapper.call_get_all_hardware_devices())
executor
¶
Executor module, implementing facilities for executing quantum programs using Classiq platform.
interface
special
¶
analyzer
special
¶
analysis_params
¶
AnalysisOptionalDevicesParams (HardwareListParams)
pydantic-model
¶
Source code in classiq/interface/analyzer/analysis_params.py
class AnalysisOptionalDevicesParams(HardwareListParams):
qubit_count: int = pydantic.Field(
default=..., description="number of qubits in the data"
)
qubit_count: int
pydantic-field
required
¶
number of qubits in the data
ChemistryGenerationParams (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/analysis_params.py
class ChemistryGenerationParams(pydantic.BaseModel):
class Config:
title = "Chemistry"
molecule: MoleculeProblem = pydantic.Field(
title="Molecule",
default=...,
description="The molecule to generate the VQE ansatz for",
)
optimizer_preferences: OptimizerPreferences = pydantic.Field(
default=..., description="Execution options for the classical Optimizer"
)
def initial_point(self) -> Optional[numpy.ndarray]:
if self.optimizer_preferences.initial_point is not None:
return numpy.ndarray(
self.optimizer_preferences.initial_point # type: ignore[arg-type]
)
else:
return None
HardwareListParams (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/analysis_params.py
class HardwareListParams(pydantic.BaseModel):
devices: Optional[List[PydanticNonEmptyString]] = pydantic.Field(
default=None, description="Devices"
)
providers: List[Provider]
from_ide: bool = Field(default=False)
@pydantic.validator("providers", always=True)
def set_default_providers(
cls, providers: Optional[List[AnalyzerProviderVendor]]
) -> List[AnalyzerProviderVendor]:
if providers is None:
providers = list(AnalyzerProviderVendor)
return providers
devices: List[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue]
pydantic-field
¶
Devices
HardwareParams (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/analysis_params.py
class HardwareParams(pydantic.BaseModel):
device: PydanticNonEmptyString = pydantic.Field(default=None, description="Devices")
provider: AnalyzerProviderVendor
device: ConstrainedStrValue
pydantic-field
¶
Devices
cytoscape_graph
¶
CytoScapeEdge (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/cytoscape_graph.py
class CytoScapeEdge(pydantic.BaseModel):
data: CytoScapeEdgeData = pydantic.Field(
default=..., description="Edge's Data, mainly the source and target of the Edge"
)
data: CytoScapeEdgeData
pydantic-field
required
¶
Edge's Data, mainly the source and target of the Edge
CytoScapeEdgeData (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/cytoscape_graph.py
class CytoScapeEdgeData(pydantic.BaseModel):
source: str = pydantic.Field(
default=..., description="the Id of the Node that is the Source of the edge"
)
target: str = pydantic.Field(
default=..., description="the Id of the Node that is the Target the edge"
)
CytoScapeGraph (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/cytoscape_graph.py
class CytoScapeGraph(pydantic.BaseModel):
nodes: List[CytoScapeNode] = pydantic.Field(
default_factory=list,
description="Nodes of the Graph",
)
edges: List[CytoScapeEdge] = pydantic.Field(
default_factory=list,
description="Edges of the Graph",
)
CytoScapeNode (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/cytoscape_graph.py
class CytoScapeNode(pydantic.BaseModel):
data: Dict[str, Any] = pydantic.Field(
default=...,
description="Data of the Node, such as label, and color, can be of free form",
)
position: Optional[CytoScapePosition] = pydantic.Field(
default=..., description="Position of the Node to be rendered in Cytocape"
)
CytoScapePosition (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/cytoscape_graph.py
class CytoScapePosition(pydantic.BaseModel):
x: int = pydantic.Field(
default=..., description="X coordinate in the Cytoscape View"
)
y: int = pydantic.Field(
default=..., description="Y coordinate in the Cytoscape View"
)
HardwareConnectivityGraphResult (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/cytoscape_graph.py
class HardwareConnectivityGraphResult(VersionedModel):
graph: Optional[CytoScapeGraph] = pydantic.Field(
default=...,
description="The Cytoscape graph in the desired Structure for the FE",
)
error: ConnectivityErrors = pydantic.Field(
default=ConnectivityErrors.EMPTY,
description="Any errors encountered while generating the graph",
)
error: ConnectivityErrors
pydantic-field
¶
Any errors encountered while generating the graph
graph: CytoScapeGraph
pydantic-field
required
¶
The Cytoscape graph in the desired Structure for the FE
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
result
¶
Analysis (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class Analysis(VersionedModel):
input_properties: QuantumCircuitProperties = pydantic.Field(
default=..., description="Input circuit properties"
)
native_properties: NativeQuantumCircuitProperties = pydantic.Field(
default=..., description="Transpiled circuit properties"
)
input_properties: QuantumCircuitProperties
pydantic-field
required
¶
Input circuit properties
native_properties: NativeQuantumCircuitProperties
pydantic-field
required
¶
Transpiled circuit properties
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
AvailableHardware (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class AvailableHardware(pydantic.BaseModel):
ibm_quantum: Optional[Dict[PydanticNonEmptyString, bool]] = pydantic.Field(
default=None,
description="available IBM Quantum devices with boolean indicates if a given device has enough qubits.",
)
azure_quantum: Optional[Dict[PydanticNonEmptyString, bool]] = pydantic.Field(
default=None,
description="available Azure Quantum devices with boolean indicates if a given device has enough qubits.",
)
amazon_braket: Optional[Dict[PydanticNonEmptyString, bool]] = pydantic.Field(
default=None,
description="available Amazon Braket devices with boolean indicates if a given device has enough qubits.",
)
amazon_braket: Dict[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, bool]
pydantic-field
¶
available Amazon Braket devices with boolean indicates if a given device has enough qubits.
azure_quantum: Dict[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, bool]
pydantic-field
¶
available Azure Quantum devices with boolean indicates if a given device has enough qubits.
ibm_quantum: Dict[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, bool]
pydantic-field
¶
available IBM Quantum devices with boolean indicates if a given device has enough qubits.
DevicesResult (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class DevicesResult(VersionedModel):
devices: AvailableHardware
status: GraphStatus
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
GraphResult (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class GraphResult(VersionedModel):
kind: Literal["graph"] = Field(default="graph")
details: str
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
HardwareComparisonData (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class HardwareComparisonData(VersionedModel):
kind: Literal["hardware_comparison"] = Field(default="hardware_comparison")
data: List[SingleHardwareInformation]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
HardwareComparisonInformation (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class HardwareComparisonInformation(pydantic.BaseModel):
devices: List[str] = pydantic.Field(
default=..., description="Device which is used for the transpilation."
)
providers: List[str] = pydantic.Field(
default=..., description="Provider cloud of the device."
)
depth: List[pydantic.NonNegativeInt] = pydantic.Field(
default=..., description="Circuit depth."
)
multi_qubit_gate_count: List[pydantic.NonNegativeInt] = pydantic.Field(
default=..., description="Number of multi qubit gates."
)
total_gate_count: List[pydantic.NonNegativeInt] = pydantic.Field(
default=..., description="Number of total gates."
)
@pydantic.root_validator
def validate_equal_length(cls, values: Dict[str, list]) -> Dict[str, list]:
lengths = list(map(len, values.values()))
if len(set(lengths)) != 1:
raise ClassiqValueError("All lists should have the same length")
return values
depth: List[pydantic.types.NonNegativeInt]
pydantic-field
required
¶
Circuit depth.
devices: List[str]
pydantic-field
required
¶
Device which is used for the transpilation.
multi_qubit_gate_count: List[pydantic.types.NonNegativeInt]
pydantic-field
required
¶
Number of multi qubit gates.
providers: List[str]
pydantic-field
required
¶
Provider cloud of the device.
total_gate_count: List[pydantic.types.NonNegativeInt]
pydantic-field
required
¶
Number of total gates.
NativeQuantumCircuitProperties (QuantumCircuitProperties)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class NativeQuantumCircuitProperties(QuantumCircuitProperties):
native_gates: Set[BasisGates] = pydantic.Field(
default=..., description="Native gates used for decomposition"
)
native_gates: Set[classiq.interface.analyzer.result.BasisGates]
pydantic-field
required
¶
Native gates used for decomposition
QuantumCircuitProperties (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class QuantumCircuitProperties(pydantic.BaseModel):
depth: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Circuit depth"
)
auxiliary_qubits: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Number of Auxiliary qubits"
)
classical_bits: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Number of classical bits"
)
gates_count: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Total number of gates in the circuit"
)
multi_qubit_gates_count: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Number of multi-qubit gates in circuit"
)
non_entangled_subcircuits_count: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Number of non-entangled sub-circuit "
)
auxiliary_qubits: NonNegativeInt
pydantic-field
required
¶
Number of Auxiliary qubits
classical_bits: NonNegativeInt
pydantic-field
required
¶
Number of classical bits
depth: NonNegativeInt
pydantic-field
required
¶
Circuit depth
gates_count: NonNegativeInt
pydantic-field
required
¶
Total number of gates in the circuit
multi_qubit_gates_count: NonNegativeInt
pydantic-field
required
¶
Number of multi-qubit gates in circuit
non_entangled_subcircuits_count: NonNegativeInt
pydantic-field
required
¶
Number of non-entangled sub-circuit
RbResults (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class RbResults(VersionedModel):
mean_fidelity: float
average_error: float
A: float
B: float
success_probability: List[float]
parameters_error: Tuple[float, ...]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
SingleHardwareInformation (BaseModel)
pydantic-model
¶
Source code in classiq/interface/analyzer/result.py
class SingleHardwareInformation(pydantic.BaseModel):
devices: str = pydantic.Field(
default=..., description="Device which is used for the transpilation."
)
providers: str = pydantic.Field(
default=..., description="Provider cloud of the device."
)
depth: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Circuit depth."
)
multi_qubit_gate_count: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Number of multi qubit gates."
)
total_gate_count: pydantic.NonNegativeInt = pydantic.Field(
default=..., description="Number of total gates."
)
depth: NonNegativeInt
pydantic-field
required
¶
Circuit depth.
devices: str
pydantic-field
required
¶
Device which is used for the transpilation.
multi_qubit_gate_count: NonNegativeInt
pydantic-field
required
¶
Number of multi qubit gates.
providers: str
pydantic-field
required
¶
Provider cloud of the device.
total_gate_count: NonNegativeInt
pydantic-field
required
¶
Number of total gates.
applications
special
¶
qsvm
¶
QSVMData (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/applications/qsvm.py
class QSVMData(VersionedModel):
data: DataList
labels: Optional[LabelsInt] = None
internal_state: Optional[QSVMInternalState] = None
preferences: QSVMPreferences
class Config:
smart_union = True
extra = "forbid"
@pydantic.validator("data", pre=True)
def set_data(cls, data: Union[IterableType, ArrayLike]) -> list:
return listify(data)
@pydantic.validator("labels", pre=True)
def set_labels(
cls, labels: Optional[Union[IterableType, ArrayLike]]
) -> Optional[list]:
if labels is None:
return None
else:
return listify(labels)
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
QSVMInternalState (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/applications/qsvm.py
class QSVMInternalState(VersionedModel):
underscore_sparse: bool
class_weight: list
classes: list
underscore_gamma: float
underscore_base_fit: list
support: list
support_vectors: list
underscore_n_support: list
dual_coef_2: list
intercept: list
underscore_p_a: list
underscore_p_b: list
fit_status: int
shape_fit: Shape
underscore_intercept: list
dual_coef: list
class_weight__shape: Shape
classes__shape: Shape
underscore_base_fit__shape: Shape
support__shape: Shape
support_vectors__shape: Shape
underscore_n_support__shape: Shape
dual_coef_2__shape: Shape
intercept__shape: Shape
underscore_p_a__shape: Shape
underscore_p_b__shape: Shape
underscore_intercept__shape: Shape
dual_coef__shape: Shape
set_class_weight = validate_array_to_list("class_weight")
set_classes = validate_array_to_list("classes")
set_underscore_base_fit = validate_array_to_list("underscore_base_fit")
set_support = validate_array_to_list("support")
set_support_vectors = validate_array_to_list("support_vectors")
set_underscore_n_support = validate_array_to_list("underscore_n_support")
set_dual_coef_2 = validate_array_to_list("dual_coef_2")
set_intercept = validate_array_to_list("intercept")
set_underscore_p_a = validate_array_to_list("underscore_p_a")
set_underscore_p_b = validate_array_to_list("underscore_p_b")
set_underscore_intercept = validate_array_to_list("underscore_intercept")
set_dual_coef = validate_array_to_list("dual_coef")
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
QSVMPredictResult (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/applications/qsvm.py
class QSVMPredictResult(VersionedModel):
data: list # serialized np.array
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
QSVMTestResult (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/applications/qsvm.py
class QSVMTestResult(VersionedModel):
data: float # between 0 to 1
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
backend
special
¶
backend_preferences
¶
AliceBobBackendPreferences (BackendPreferences)
pydantic-model
¶
Source code in classiq/interface/backend/backend_preferences.py
class AliceBobBackendPreferences(BackendPreferences):
backend_service_provider: ProviderTypeVendor.ALICE_BOB
api_key: pydantic_backend.PydanticAliceBobApiKeyType = pydantic.Field(
..., description="AliceBob API key"
)
@pydantic.root_validator(pre=True)
def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
return values_with_discriminator(
values, "backend_service_provider", ProviderVendor.ALICE_AND_BOB
)
api_key: ConstrainedStrValue
pydantic-field
required
¶
AliceBob API key
AwsBackendPreferences (BackendPreferences)
pydantic-model
¶
Source code in classiq/interface/backend/backend_preferences.py
class AwsBackendPreferences(BackendPreferences):
backend_service_provider: ProviderTypeVendor.AMAZON_BRAKET
aws_role_arn: pydantic_backend.PydanticAwsRoleArn = pydantic.Field(
description="ARN of the role to be assumed for execution on your Braket account."
)
s3_bucket_name: str = pydantic.Field(description="S3 Bucket Name")
s3_folder: pydantic_backend.PydanticS3BucketKey = pydantic.Field(
description="S3 Folder Path Within The S3 Bucket"
)
job_timeout: pydantic_backend.PydanticExecutionTimeout = pydantic.Field(
description="Timeout for Jobs sent for execution in seconds.",
default=AWS_DEFAULT_JOB_TIMEOUT_SECONDS,
)
@validator("s3_bucket_name")
def _validate_s3_bucket_name(
cls, s3_bucket_name: str, values: Dict[str, Any]
) -> str:
s3_bucket_name = s3_bucket_name.strip()
if not s3_bucket_name.startswith("amazon-braket-"):
raise ClassiqValueError('S3 bucket name should start with "amazon-braket-"')
return s3_bucket_name
@pydantic.root_validator(pre=True)
def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
return values_with_discriminator(
values, "backend_service_provider", ProviderVendor.AMAZON_BRAKET
)
aws_role_arn: ConstrainedStrValue
pydantic-field
required
¶
ARN of the role to be assumed for execution on your Braket account.
job_timeout: ConstrainedIntValue
pydantic-field
¶
Timeout for Jobs sent for execution in seconds.
s3_bucket_name: str
pydantic-field
required
¶
S3 Bucket Name
s3_folder: ConstrainedStrValue
pydantic-field
required
¶
S3 Folder Path Within The S3 Bucket
AzureBackendPreferences (BackendPreferences)
pydantic-model
¶
Source code in classiq/interface/backend/backend_preferences.py
class AzureBackendPreferences(BackendPreferences):
backend_service_provider: ProviderTypeVendor.AZURE_QUANTUM
location: str = pydantic.Field(
default="East US", description="Azure personal resource region"
)
credentials: Optional[AzureCredential] = pydantic.Field(
default=None,
description="The service principal credential to access personal quantum workspace",
)
@property
def run_through_classiq(self) -> bool:
return self.credentials is None
@pydantic.root_validator(pre=True)
def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
return values_with_discriminator(
values, "backend_service_provider", ProviderVendor.AZURE_QUANTUM
)
AzureCredential (BaseSettings)
pydantic-model
¶
Source code in classiq/interface/backend/backend_preferences.py
class AzureCredential(pydantic.BaseSettings):
tenant_id: str = pydantic.Field(description="Azure Tenant ID")
client_id: str = pydantic.Field(description="Azure Client ID")
client_secret: str = pydantic.Field(description="Azure Client Secret")
resource_id: pydantic_backend.PydanticAzureResourceIDType = pydantic.Field(
description="Azure Resource ID (including Azure subscription ID, resource "
"group and workspace), for personal resource",
)
class Config:
title = "Azure Service Principal Credential"
env_prefix = "AZURE_"
case_sensitive = False
client_id: str
pydantic-field
required
¶
Azure Client ID
client_secret: str
pydantic-field
required
¶
Azure Client Secret
resource_id: ConstrainedStrValue
pydantic-field
required
¶
Azure Resource ID (including Azure subscription ID, resource group and workspace), for personal resource
tenant_id: str
pydantic-field
required
¶
Azure Tenant ID
BackendPreferences (BaseModel)
pydantic-model
¶
Source code in classiq/interface/backend/backend_preferences.py
class BackendPreferences(BaseModel):
# Due to the way the field is currently implemented, i.e. it redefined with different types
# in the subclass, it shouldn't be dumped with exclude_unset. This causes this field not to appear.
# For example: don't use obj.dict(exclude_unset=True).
backend_service_provider: str = pydantic.Field(
..., description="Provider company or cloud for the requested backend."
)
backend_name: str = pydantic.Field(
..., description="Name of the requested backend or target."
)
@property
def hw_provider(self) -> Provider:
return Provider(self.backend_service_provider)
@pydantic.validator("backend_service_provider", pre=True)
def validate_backend_service_provider(
cls, backend_service_provider: Any
) -> Provider:
return validate_backend_service_provider(backend_service_provider)
@classmethod
def batch_preferences(
cls, *, backend_names: Iterable[str], **kwargs: Any
) -> List[BackendPreferences]:
return [cls(backend_name=name, **kwargs) for name in backend_names]
def is_nvidia_backend(self) -> bool:
return False
IBMBackendPreferences (BackendPreferences)
pydantic-model
¶
Source code in classiq/interface/backend/backend_preferences.py
class IBMBackendPreferences(BackendPreferences):
backend_service_provider: ProviderTypeVendor.IBM_QUANTUM
access_token: Optional[str] = pydantic.Field(
default=None,
description="IBM Quantum access token to be used"
" with IBM Quantum hosted backends",
)
provider: IBMBackendProvider = pydantic.Field(
default_factory=IBMBackendProvider,
description="Provider specs. for identifying a single IBM Quantum provider.",
)
@pydantic.root_validator(pre=True)
def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
return values_with_discriminator(
values, "backend_service_provider", ProviderVendor.IBM_QUANTUM
)
IonqBackendPreferences (BackendPreferences)
pydantic-model
¶
Source code in classiq/interface/backend/backend_preferences.py
class IonqBackendPreferences(BackendPreferences):
backend_service_provider: ProviderTypeVendor.IONQ
api_key: pydantic_backend.PydanticIonQApiKeyType = pydantic.Field(
..., description="IonQ API key"
)
@pydantic.root_validator(pre=True)
def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
return values_with_discriminator(
values, "backend_service_provider", ProviderVendor.IONQ
)
api_key: ConstrainedStrValue
pydantic-field
required
¶
IonQ API key
OQCBackendPreferences (BackendPreferences)
pydantic-model
¶
Source code in classiq/interface/backend/backend_preferences.py
class OQCBackendPreferences(BackendPreferences):
backend_service_provider: ProviderTypeVendor.OQC
username: str = pydantic.Field(description="OQC username")
password: str = pydantic.Field(description="OQC password")
@pydantic.root_validator(pre=True)
def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
return values_with_discriminator(
values, "backend_service_provider", ProviderVendor.OQC
)
chemistry
special
¶
fermionic_operator
¶
FermionicOperator (HashablePydanticBaseModel)
pydantic-model
¶
Specification of a Fermionic operator. Input: List of ladder operators, each ladder operator is described by a tuple of its index and a character indicating if it's a creation ('+') or annihilation operator ('-').
Source code in classiq/interface/chemistry/fermionic_operator.py
class FermionicOperator(HashablePydanticBaseModel):
"""
Specification of a Fermionic operator.
Input:
List of ladder operators, each ladder operator is described by a tuple of its
index and a character indicating if it's a creation ('+') or annihilation operator ('-').
"""
op_list: list = pydantic.Field(
description="A list of tuples each containing an index and a character; for example [('+', 0), ('-', 1)].",
)
@staticmethod
def _validate_single_op(op: tuple) -> LadderOperator:
if not isinstance(op, tuple):
try: # type: ignore[unreachable] # it is reachable...
op = tuple(op)
except Exception as exc:
raise ClassiqValueError("Ladder operator should be a tuple.") from exc
if len(op) != 2:
raise ClassiqValueError(
"Ladder operator tuple should be of length two; for example (1, '+')."
)
if op[0] not in ("+", "-"):
raise ClassiqValueError(
"The first term in a ladder operator tuple indicates if its a raising ('+')"
f" or lowering ('-') operator. Allowed input is: '+' or '-', received {op[0]}"
)
if not isinstance(op[1], int):
raise ClassiqValueError(
"The second term in a ladder operator tuple indicates its index and should be of type int"
)
return op # type: ignore[return-value] # mypy thinks that it is `Tuple[Any, ...]`, though the asserts here tell otherwise..
@pydantic.validator("op_list")
def _validate_op_list(cls, op_list: list) -> list:
return list(map(cls._validate_single_op, op_list))
def __mul__(self, coeff: Union[float, int]) -> SummedFermionicOperator:
if isinstance(coeff, (float, int)):
return SummedFermionicOperator(op_list=[(self, float(coeff))])
raise ClassiqValueError(
"The coefficient multiplying Fermionic Operator should be of type float"
)
__rmul__ = __mul__
def __add__(
self, other: Union[SummedFermionicOperator, FermionicOperator]
) -> SummedFermionicOperator:
if isinstance(other, SummedFermionicOperator):
return SummedFermionicOperator(op_list=[(self, 1.0)] + other.op_list)
elif isinstance(other, FermionicOperator):
return SummedFermionicOperator(op_list=[(self, 1.0)] + [(other, 1.0)])
raise ClassiqValueError(
"FermionicOperator can be summed together only with type FermionicOperator or SummedFermionicOperator"
)
class Config:
frozen = True
@staticmethod
def _to_ladder_op(char: str) -> str:
return "a" + _SUPERSCRIPT_PLUS if char == "+" else "a"
@staticmethod
def _to_subscript(num: int) -> str:
return "".join(_SUBSCRIPT_UNICODE_CHARS[digit] for digit in str(num))
def __str__(self) -> str:
return "".join(
f"{self._to_ladder_op(char)}{self._to_subscript(index)}"
for (char, index) in self.op_list
)
@property
def all_indices(self) -> Set[int]:
return {op[1] for op in self.op_list}
op_list: list
pydantic-field
required
¶
A list of tuples each containing an index and a character; for example [('+', 0), ('-', 1)].
__str__(self)
special
¶
Return str(self).
Source code in classiq/interface/chemistry/fermionic_operator.py
def __str__(self) -> str:
return "".join(
f"{self._to_ladder_op(char)}{self._to_subscript(index)}"
for (char, index) in self.op_list
)
SummedFermionicOperator (HashablePydanticBaseModel)
pydantic-model
¶
Specification of a summed Fermionic operator. Input: List of fermionic operators tuples, The first term in the tuple is the FermionicOperator and the second term is its coefficient. For example: op1 = FermionicOperator(op_list=[('+', 0), ('-', 1)]) op2 = FermionicOperator(op_list=[('-', 0), ('-', 1)]) summed_operator = SummedFermionicOperator(op_list=[(op1, 0.2), (op2, 6.7)])
Source code in classiq/interface/chemistry/fermionic_operator.py
class SummedFermionicOperator(HashablePydanticBaseModel):
"""
Specification of a summed Fermionic operator.
Input:
List of fermionic operators tuples, The first term in the tuple is the FermionicOperator and the second term is its coefficient.
For example:
op1 = FermionicOperator(op_list=[('+', 0), ('-', 1)])
op2 = FermionicOperator(op_list=[('-', 0), ('-', 1)])
summed_operator = SummedFermionicOperator(op_list=[(op1, 0.2), (op2, 6.7)])
"""
op_list: list = pydantic.Field(
description="A list of tuples each containing a FermionicOperator and a coefficient.",
)
class Config:
frozen = True
@staticmethod
def _validate_single_op(op: tuple) -> FermionicOperatorTuple:
# is it tuple - if not, convert to tuple
if not isinstance(op, tuple):
try: # type: ignore[unreachable] # it is reachable...
op = tuple(op)
except Exception as exc:
raise ClassiqValueError("Operator should be a tuple.") from exc
if len(op) != 2:
raise ClassiqValueError("Operator tuple should be of length two.")
# is it FermionicOperator - if not, convert to FermionicOperator
if not isinstance(op[0], FermionicOperator):
try:
op = (FermionicOperator(**op[0]), op[1])
except Exception as exc:
raise ClassiqValueError(
"The first term in the operator tuple should be an instance of the FermionicOperator class"
) from exc
if not isinstance(op[1], float):
raise ClassiqValueError(
"The second term in the operator tuple indicates its coefficient and should be of type float"
)
return op # type: ignore[return-value] # mypy thinks that it is `Tuple[Any, ...]`, though the asserts here tell otherwise..
@pydantic.validator("op_list")
def _validate_op_list(cls, op_list: list) -> list:
return list(map(cls._validate_single_op, op_list))
def __add__(
self, other: Union[SummedFermionicOperator, FermionicOperator]
) -> SummedFermionicOperator:
if isinstance(other, SummedFermionicOperator):
return SummedFermionicOperator(op_list=self.op_list + other.op_list)
elif isinstance(other, FermionicOperator):
return SummedFermionicOperator(op_list=self.op_list + [(other, 1.0)])
raise ClassiqValueError(
"FermionicOperator can be summed together only with type FermionicOperator or SummedFermionicOperator"
)
def is_close(self, other: SummedFermionicOperator) -> bool:
if not isinstance(other, SummedFermionicOperator):
return False # type: ignore[unreachable]
if len(self.op_list) != len(other.op_list):
return False
for (op1, coeff1), (op2, coeff2) in zip(self.op_list, other.op_list):
if op1 != op2 or not np.isclose(coeff1, coeff2):
return False
return True
@property
def _all_indices(self) -> Set[int]:
return set(
itertools.chain.from_iterable(op.all_indices for op, _ in self.op_list)
)
@property
def num_qubits(self) -> int:
return len(self._all_indices)
def __str__(self) -> str:
return " + \n".join(str(op[1]) + " * " + str(op[0]) for op in self.op_list)
op_list: list
pydantic-field
required
¶
A list of tuples each containing a FermionicOperator and a coefficient.
__str__(self)
special
¶
Return str(self).
Source code in classiq/interface/chemistry/fermionic_operator.py
def __str__(self) -> str:
return " + \n".join(str(op[1]) + " * " + str(op[0]) for op in self.op_list)
ground_state_problem
¶
GroundStateProblem (HashablePydanticBaseModel)
pydantic-model
¶
Source code in classiq/interface/chemistry/ground_state_problem.py
class GroundStateProblem(HashablePydanticBaseModel):
kind: str
mapping: FermionMapping = pydantic.Field(
default=FermionMapping.JORDAN_WIGNER,
description="Fermionic mapping type",
title="Fermion Mapping",
)
z2_symmetries: bool = pydantic.Field(
default=False,
description="whether to perform z2 symmetries reduction",
)
num_qubits: Optional[int] = pydantic.Field(default=None)
@pydantic.validator("z2_symmetries")
def _validate_z2_symmetries(
cls, z2_symmetries: bool, values: Dict[str, Any]
) -> bool:
if z2_symmetries and values.get("mapping") == FermionMapping.FAST_BRAVYI_KITAEV:
raise ClassiqValueError(
"z2 symmetries reduction can not be used for fast_bravyi_kitaev mapping"
)
return z2_symmetries
class Config:
frozen = True
HamiltonianProblem (GroundStateProblem)
pydantic-model
¶
Source code in classiq/interface/chemistry/ground_state_problem.py
class HamiltonianProblem(GroundStateProblem):
kind: Literal["hamiltonian"] = pydantic.Field(default="hamiltonian")
hamiltonian: SummedFermionicOperator = pydantic.Field(
description="Hamiltonian as a fermionic operator"
)
num_particles: List[pydantic.PositiveInt] = pydantic.Field(
description="Tuple containing the numbers of alpha particles and beta particles"
)
@pydantic.validator("num_particles")
def _validate_num_particles(cls, num_particles: List[int]) -> List[int]:
assert isinstance(num_particles, list)
assert len(num_particles) == 2
# This probably will never happen, since pydantic automatically converts
# floats to ints
assert isinstance(num_particles[0], int)
assert num_particles[0] >= 1
assert isinstance(num_particles[1], int)
assert num_particles[1] >= 1
return num_particles
MoleculeProblem (GroundStateProblem)
pydantic-model
¶
Source code in classiq/interface/chemistry/ground_state_problem.py
class MoleculeProblem(GroundStateProblem):
kind: Literal["molecule"] = pydantic.Field(default="molecule")
molecule: Molecule
basis: str = pydantic.Field(default="sto3g", description="Molecular basis set")
freeze_core: bool = pydantic.Field(default=False)
remove_orbitals: List[int] = pydantic.Field(
default_factory=list, description="list of orbitals to remove"
)
molecule
¶
Atom (HashablePydanticBaseModel)
pydantic-model
¶
Source code in classiq/interface/chemistry/molecule.py
class Atom(HashablePydanticBaseModel):
symbol: Literal[tuple(ELEMENTS)] = pydantic.Field(description="The atom symbol") # type: ignore[valid-type]
x: float = pydantic.Field(description="The x coordinate of the atom")
y: float = pydantic.Field(description="The y coordinate of the atom")
z: float = pydantic.Field(description="The z coordinate of the atom")
symbol: Literal['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Nh', 'Fl', 'Mc', 'Lv', 'Ts', 'Og']
pydantic-field
required
¶
The atom symbol
x: float
pydantic-field
required
¶
The x coordinate of the atom
y: float
pydantic-field
required
¶
The y coordinate of the atom
z: float
pydantic-field
required
¶
The z coordinate of the atom
Molecule (HashablePydanticBaseModel)
pydantic-model
¶
Source code in classiq/interface/chemistry/molecule.py
class Molecule(HashablePydanticBaseModel):
atoms: List[Atom] = pydantic.Field(
description="A list of atoms each containing the atoms symbol and its (x,y,z) location",
min_items=1,
)
spin: pydantic.NonNegativeInt = pydantic.Field(
default=1, description="spin of the molecule"
)
charge: pydantic.NonNegativeInt = pydantic.Field(
default=0, description="charge of the molecule"
)
@property
def atoms_type(self) -> List[AtomType]:
return [(atom.symbol, [atom.x, atom.y, atom.z]) for atom in self.atoms]
@pydantic.validator("atoms", each_item=True, pre=True)
def _validate_atoms(cls, atom: Union[AtomType, Atom]) -> Atom:
if isinstance(atom, (list, tuple)):
return cls._validate_old_atoms_type(atom)
return atom
@staticmethod
def _validate_old_atoms_type(atom: AtomType) -> Atom:
if len(atom) != 2:
raise ClassiqValueError(
"each atom should be a list of two entries: 1) name pf the elemnt (str) 2) list of its (x,y,z) location"
)
if not isinstance(atom[0], str):
raise ClassiqValueError(
f"atom name should be a string. unknown element: {atom[0]}."
)
if len(atom[1]) != 3:
raise ClassiqValueError(
f"location of the atom is of length three, representing the (x,y,z) coordinates of the atom, error value: {atom[1]}"
)
for idx in atom[1]:
if not isinstance(idx, (float, int)):
raise ClassiqValueError(
f"coordinates of the atom should be of type float. error value: {idx}"
)
symbol, coordinate = atom
return Atom(symbol=symbol, x=coordinate[0], y=coordinate[1], z=coordinate[2])
class Config:
frozen = True
operator
¶
PauliOperator (HashablePydanticBaseModel, VersionedModel)
pydantic-model
¶
Specification of a Pauli sum operator.
Source code in classiq/interface/chemistry/operator.py
class PauliOperator(HashablePydanticBaseModel, VersionedModel):
"""
Specification of a Pauli sum operator.
"""
pauli_list: PydanticPauliList = pydantic.Field(
description="A list of tuples each containing a pauli string comprised of I,X,Y,Z characters and a complex coefficient; for example [('IZ', 0.1), ('XY', 0.2)].",
)
is_hermitian: bool = pydantic.Field(default=False)
has_complex_coefficients: bool = pydantic.Field(default=True)
def show(self) -> str:
if self.is_hermitian:
# If the operator is hermitian then the coefficients must be numeric
return "\n".join(
f"{summand[1].real:+.3f} * {summand[0]}" for summand in self.pauli_list # type: ignore[union-attr]
)
return "\n".join(
f"+({summand[1]:+.3f}) * {summand[0]}" for summand in self.pauli_list
)
@pydantic.validator("pauli_list", each_item=True, pre=True)
def _validate_pauli_monomials(
cls, monomial: Tuple[PydanticPauliMonomialStr, ParameterComplexType]
) -> Tuple[PydanticPauliMonomialStr, ParameterComplexType]:
_PauliMonomialLengthValidator( # type: ignore[call-arg]
monomial=monomial
) # Validate the length of the monomial.
coeff = cls._validate_monomial_coefficient(monomial[1])
parsed_monomial = _PauliMonomialParser(string=monomial[0], coeff=coeff) # type: ignore[call-arg]
return (parsed_monomial.string, parsed_monomial.coeff)
@staticmethod
def _validate_monomial_coefficient(
coeff: Union[sympy.Expr, ParameterComplexType]
) -> ParameterComplexType:
if isinstance(coeff, str):
validate_expression_str(coeff)
elif isinstance(coeff, sympy.Expr):
coeff = str(coeff)
return coeff
@pydantic.validator("pauli_list")
def _validate_pauli_list(cls, pauli_list: PydanticPauliList) -> PydanticPauliList:
if not all_equal(len(summand[0]) for summand in pauli_list):
raise ClassiqValueError("Pauli strings have incompatible lengths.")
return pauli_list
@pydantic.root_validator
def _validate_hermitianity(cls, values: Dict[str, Any]) -> Dict[str, Any]:
pauli_list = values.get("pauli_list", [])
if all(isinstance(summand[1], complex) for summand in pauli_list):
values["is_hermitian"] = all(
np.isclose(complex(summand[1]).real, summand[1])
for summand in pauli_list
)
if values.get("is_hermitian", False):
values["has_complex_coefficients"] = False
values["pauli_list"] = [
(summand[0], complex(summand[1].real)) for summand in pauli_list
]
else:
values["has_complex_coefficients"] = not all(
np.isclose(complex(summand[1]).real, summand[1])
for summand in pauli_list
if isinstance(summand[1], complex)
)
return values
def __mul__(self, coefficient: complex) -> "PauliOperator":
multiplied_ising = [
(monomial[0], self._multiply_monomial_coefficient(monomial[1], coefficient))
for monomial in self.pauli_list
]
return self.__class__(pauli_list=multiplied_ising)
@staticmethod
def _multiply_monomial_coefficient(
monomial_coefficient: ParameterComplexType, coefficient: complex
) -> ParameterComplexType:
if isinstance(monomial_coefficient, ParameterType):
return str(sympy.sympify(monomial_coefficient) * coefficient)
return monomial_coefficient * coefficient
@property
def is_commutative(self) -> bool:
return all(
self._is_sub_pauli_commutative(
[summand[0][qubit_num] for summand in self.pauli_list]
)
for qubit_num in range(self.num_qubits)
)
@staticmethod
def _is_sub_pauli_commutative(qubit_pauli_string: Union[List[str], str]) -> bool:
unique_paulis = set(qubit_pauli_string) - {"I"}
return len(unique_paulis) <= 1
@property
def num_qubits(self) -> int:
return len(self.pauli_list[0][0])
def to_matrix(self) -> np.ndarray:
if not all(isinstance(summand[1], complex) for summand in self.pauli_list):
raise ClassiqValueError(
"Supporting only Hamiltonian with numeric coefficients."
)
return sum(
cast(complex, summand[1]) * to_pauli_matrix(summand[0])
for summand in self.pauli_list
) # type: ignore[return-value]
@staticmethod
def _extend_pauli_string(
pauli_string: PydanticPauliMonomialStr, num_extra_qubits: int
) -> PydanticPauliMonomialStr:
return "I" * num_extra_qubits + pauli_string
def extend(self, num_extra_qubits: int) -> "PauliOperator":
new_pauli_list = [
(self._extend_pauli_string(pauli_string, num_extra_qubits), coeff)
for (pauli_string, coeff) in self.pauli_list
]
return self.copy(update={"pauli_list": new_pauli_list}, deep=True)
@staticmethod
def _reorder_pauli_string(
pauli_string: PydanticPauliMonomialStr,
order: Collection[int],
new_num_qubits: int,
) -> PydanticPauliMonomialStr:
reversed_pauli_string = pauli_string[::-1]
reversed_new_pauli_string = ["I"] * new_num_qubits
for logical_pos, actual_pos in enumerate(order):
reversed_new_pauli_string[actual_pos] = reversed_pauli_string[logical_pos]
return "".join(reversed(reversed_new_pauli_string))
@staticmethod
def _validate_reorder(
order: Collection[int],
num_qubits: int,
num_extra_qubits: int,
) -> None:
if num_extra_qubits < 0:
raise ClassiqValueError("Number of extra qubits cannot be negative")
if len(order) != num_qubits:
raise ClassiqValueError("The qubits order doesn't match the Pauli operator")
if len(order) != len(set(order)):
raise ClassiqValueError("The qubits order is not one-to-one")
if not all(pos < num_qubits + num_extra_qubits for pos in order):
raise ClassiqValueError(
"The qubits order contains qubits which do no exist"
)
@classmethod
def reorder(
cls,
operator: "PauliOperator",
order: Collection[int],
num_extra_qubits: int = 0,
) -> "PauliOperator":
cls._validate_reorder(order, operator.num_qubits, num_extra_qubits)
new_num_qubits = operator.num_qubits + num_extra_qubits
new_pauli_list = [
(cls._reorder_pauli_string(pauli_string, order, new_num_qubits), coeff)
for pauli_string, coeff in operator.pauli_list
]
return cls(pauli_list=new_pauli_list)
@classmethod
def from_unzipped_lists(
cls,
operators: List[List[Pauli]],
coefficients: Optional[List[complex]] = None,
) -> "PauliOperator":
if coefficients is None:
coefficients = [1] * len(operators)
if len(operators) != len(coefficients):
raise ClassiqValueError(
f"The number of coefficients ({len(coefficients)}) must be equal to the number of pauli operators ({len(operators)})"
)
return cls(
pauli_list=[
(pauli_integers_to_str(op), coeff)
for op, coeff in zip(operators, coefficients)
]
)
class Config:
frozen = True
pauli_list: ConstrainedListValue
pydantic-field
required
¶
A list of tuples each containing a pauli string comprised of I,X,Y,Z characters and a complex coefficient; for example [('IZ', 0.1), ('XY', 0.2)].
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
PauliOperatorV1 (HashablePydanticBaseModel)
pydantic-model
¶
Specification of a Pauli sum operator.
Source code in classiq/interface/chemistry/operator.py
class PauliOperatorV1(HashablePydanticBaseModel):
"""
Specification of a Pauli sum operator.
"""
pauli_list: PydanticPauliList = pydantic.Field(
description="A list of tuples each containing a pauli string comprised of I,X,Y,Z characters and a complex coefficient; for example [('IZ', 0.1), ('XY', 0.2)].",
)
is_hermitian: bool = pydantic.Field(default=False)
has_complex_coefficients: bool = pydantic.Field(default=True)
def show(self) -> str:
if self.is_hermitian:
# If the operator is hermitian then the coefficients must be numeric
return "\n".join(
f"{summand[1].real:+.3f} * {summand[0]}" for summand in self.pauli_list # type: ignore[union-attr]
)
return "\n".join(
f"+({summand[1]:+.3f}) * {summand[0]}" for summand in self.pauli_list
)
@pydantic.validator("pauli_list", each_item=True, pre=True)
def _validate_pauli_monomials(
cls, monomial: Tuple[PydanticPauliMonomialStr, ParameterComplexType]
) -> Tuple[PydanticPauliMonomialStr, ParameterComplexType]:
_PauliMonomialLengthValidator( # type: ignore[call-arg]
monomial=monomial
) # Validate the length of the monomial.
coeff = cls._validate_monomial_coefficient(monomial[1])
parsed_monomial = _PauliMonomialParser(string=monomial[0], coeff=coeff) # type: ignore[call-arg]
return (parsed_monomial.string, parsed_monomial.coeff)
@staticmethod
def _validate_monomial_coefficient(
coeff: Union[sympy.Expr, ParameterComplexType]
) -> ParameterComplexType:
if isinstance(coeff, str):
validate_expression_str(coeff)
elif isinstance(coeff, sympy.Expr):
coeff = str(coeff)
return coeff
@pydantic.validator("pauli_list")
def _validate_pauli_list(cls, pauli_list: PydanticPauliList) -> PydanticPauliList:
if not all_equal(len(summand[0]) for summand in pauli_list):
raise ClassiqValueError("Pauli strings have incompatible lengths.")
return pauli_list
@pydantic.root_validator
def _validate_hermitianity(cls, values: Dict[str, Any]) -> Dict[str, Any]:
pauli_list = values.get("pauli_list", [])
if all(isinstance(summand[1], complex) for summand in pauli_list):
values["is_hermitian"] = all(
np.isclose(complex(summand[1]).real, summand[1])
for summand in pauli_list
)
if values.get("is_hermitian", False):
values["has_complex_coefficients"] = False
values["pauli_list"] = [
(summand[0], complex(summand[1].real)) for summand in pauli_list
]
else:
values["has_complex_coefficients"] = not all(
np.isclose(complex(summand[1]).real, summand[1])
for summand in pauli_list
if isinstance(summand[1], complex)
)
return values
def __mul__(self, coefficient: complex) -> "PauliOperatorV1":
multiplied_ising = [
(monomial[0], self._multiply_monomial_coefficient(monomial[1], coefficient))
for monomial in self.pauli_list
]
return self.__class__(pauli_list=multiplied_ising)
@staticmethod
def _multiply_monomial_coefficient(
monomial_coefficient: ParameterComplexType, coefficient: complex
) -> ParameterComplexType:
if isinstance(monomial_coefficient, ParameterType):
return str(sympy.sympify(monomial_coefficient) * coefficient)
return monomial_coefficient * coefficient
@property
def is_commutative(self) -> bool:
return all(
self._is_sub_pauli_commutative(
[summand[0][qubit_num] for summand in self.pauli_list]
)
for qubit_num in range(self.num_qubits)
)
@staticmethod
def _is_sub_pauli_commutative(qubit_pauli_string: Union[List[str], str]) -> bool:
unique_paulis = set(qubit_pauli_string) - {"I"}
return len(unique_paulis) <= 1
@property
def num_qubits(self) -> int:
return len(self.pauli_list[0][0])
def to_matrix(self) -> np.ndarray:
if not all(isinstance(summand[1], complex) for summand in self.pauli_list):
raise ClassiqValueError(
"Supporting only Hamiltonian with numeric coefficients."
)
return sum(
cast(complex, summand[1]) * to_pauli_matrix(summand[0])
for summand in self.pauli_list
) # type: ignore[return-value]
@staticmethod
def _extend_pauli_string(
pauli_string: PydanticPauliMonomialStr, num_extra_qubits: int
) -> PydanticPauliMonomialStr:
return "I" * num_extra_qubits + pauli_string
def extend(self, num_extra_qubits: int) -> "PauliOperatorV1":
new_pauli_list = [
(self._extend_pauli_string(pauli_string, num_extra_qubits), coeff)
for (pauli_string, coeff) in self.pauli_list
]
return self.copy(update={"pauli_list": new_pauli_list}, deep=True)
@staticmethod
def _reorder_pauli_string(
pauli_string: PydanticPauliMonomialStr,
order: Collection[int],
new_num_qubits: int,
) -> PydanticPauliMonomialStr:
reversed_pauli_string = pauli_string[::-1]
reversed_new_pauli_string = ["I"] * new_num_qubits
for logical_pos, actual_pos in enumerate(order):
reversed_new_pauli_string[actual_pos] = reversed_pauli_string[logical_pos]
return "".join(reversed(reversed_new_pauli_string))
@staticmethod
def _validate_reorder(
order: Collection[int],
num_qubits: int,
num_extra_qubits: int,
) -> None:
if num_extra_qubits < 0:
raise ClassiqValueError("Number of extra qubits cannot be negative")
if len(order) != num_qubits:
raise ClassiqValueError("The qubits order doesn't match the Pauli operator")
if len(order) != len(set(order)):
raise ClassiqValueError("The qubits order is not one-to-one")
if not all(pos < num_qubits + num_extra_qubits for pos in order):
raise ClassiqValueError(
"The qubits order contains qubits which do no exist"
)
@classmethod
def reorder(
cls,
operator: "PauliOperatorV1",
order: Collection[int],
num_extra_qubits: int = 0,
) -> "PauliOperatorV1":
cls._validate_reorder(order, operator.num_qubits, num_extra_qubits)
new_num_qubits = operator.num_qubits + num_extra_qubits
new_pauli_list = [
(cls._reorder_pauli_string(pauli_string, order, new_num_qubits), coeff)
for pauli_string, coeff in operator.pauli_list
]
return cls(pauli_list=new_pauli_list)
@classmethod
def from_unzipped_lists(
cls,
operators: List[List[Pauli]],
coefficients: Optional[List[complex]] = None,
) -> "PauliOperatorV1":
if coefficients is None:
coefficients = [1] * len(operators)
if len(operators) != len(coefficients):
raise ClassiqValueError(
f"The number of coefficients ({len(coefficients)}) must be equal to the number of pauli operators ({len(operators)})"
)
return cls(
pauli_list=[
(pauli_integers_to_str(op), coeff)
for op, coeff in zip(operators, coefficients)
]
)
class Config:
frozen = True
pauli_list: ConstrainedListValue
pydantic-field
required
¶
A list of tuples each containing a pauli string comprised of I,X,Y,Z characters and a complex coefficient; for example [('IZ', 0.1), ('XY', 0.2)].
PauliOperators (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/chemistry/operator.py
class PauliOperators(VersionedModel):
operators: List[PauliOperator]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
combinatorial_optimization
special
¶
mht_qaoa_input
¶
MhtQaoaInput (BaseModel)
pydantic-model
¶
Source code in classiq/interface/combinatorial_optimization/mht_qaoa_input.py
class MhtQaoaInput(BaseModel):
reps: pydantic.PositiveInt = pydantic.Field(
default=3, description="Number of QAOA layers."
)
plot_list: List[PlotData] = pydantic.Field(
description="The list of (x,y,t) plots of the MHT problem."
)
misdetection_maximum_time_steps: pydantic.NonNegativeInt = pydantic.Field(
default=0,
description="The maximum number of time steps a target might be misdetected.",
)
penalty_energy: float = pydantic.Field(
default=2,
description="Penalty energy for invalid solutions. The value affects "
"the converges rate. Small positive values are preferred",
)
three_local_coeff: float = pydantic.Field(
default=0,
description="Coefficient for the 3-local terms in the Hamiltonian. It is related to the angular acceleration.",
)
one_local_coeff: float = pydantic.Field(
default=0, description="Coefficient for the 1-local terms in the Hamiltonian."
)
is_penalty: bool = pydantic.Field(
default=True, description="Build Pubo using penalty terms"
)
max_velocity: float = pydantic.Field(
default=0, description="Max allowed velocity for a segment"
)
def is_valid_cost(self, cost: float) -> bool:
return True
@pydantic.validator("plot_list")
def round_plot_list_times_and_validate(
cls, plot_list: List[PlotData]
) -> List[PlotData]:
MhtQaoaInput._check_all_ids_are_distinct(plot_list)
MhtQaoaInput._round_to_tolerance_decimals(plot_list)
time_stamps = sorted({plot.t for plot in plot_list})
time_diff_set = {
np.round(time_stamps[i] - time_stamps[i - 1], decimals=_TOLERANCE_DECIMALS)
for i in range(1, len(time_stamps))
}
if len(time_diff_set) != 1:
raise ClassiqValueError(
"The time difference between each time stamp is not equal"
)
return plot_list
@staticmethod
def _round_to_tolerance_decimals(plot_list: List[PlotData]) -> None:
for plot in plot_list:
plot.t = np.round(plot.t, decimals=_TOLERANCE_DECIMALS)
@staticmethod
def _check_all_ids_are_distinct(plot_list: List[PlotData]) -> None:
if not more_itertools.all_unique(plot.plot_id for plot in plot_list):
raise ClassiqValueError("Plot IDs should be unique.")
is_penalty: bool
pydantic-field
¶
Build Pubo using penalty terms
max_velocity: float
pydantic-field
¶
Max allowed velocity for a segment
misdetection_maximum_time_steps: NonNegativeInt
pydantic-field
¶
The maximum number of time steps a target might be misdetected.
one_local_coeff: float
pydantic-field
¶
Coefficient for the 1-local terms in the Hamiltonian.
penalty_energy: float
pydantic-field
¶
Penalty energy for invalid solutions. The value affects the converges rate. Small positive values are preferred
plot_list: List[classiq.interface.combinatorial_optimization.mht_qaoa_input.PlotData]
pydantic-field
required
¶
The list of (x,y,t) plots of the MHT problem.
reps: PositiveInt
pydantic-field
¶
Number of QAOA layers.
three_local_coeff: float
pydantic-field
¶
Coefficient for the 3-local terms in the Hamiltonian. It is related to the angular acceleration.
PlotData (BaseModel)
pydantic-model
¶
Source code in classiq/interface/combinatorial_optimization/mht_qaoa_input.py
class PlotData(BaseModel):
# We are currently ignoring units. This might need to be handled in the future
x: float = pydantic.Field(description="The X coordinate of this plot")
y: float = pydantic.Field(description="The Y coordinate of this plot")
t: float = pydantic.Field(description="The time stamp of this plot")
plot_id: pydantic.NonNegativeInt = pydantic.Field(
description="The plot ID of this plot"
)
optimization_problem
¶
MaxCutProblem (BaseModel)
pydantic-model
¶
Source code in classiq/interface/combinatorial_optimization/optimization_problem.py
class MaxCutProblem(BaseModel):
qaoa_reps: pydantic.PositiveInt = pydantic.Field(
default=1, description="Number of layers in qaoa ansatz."
)
optimizer_preferences: CombinatorialOptimizer = pydantic.Field(
default_factory=CombinatorialOptimizer,
description="preferences for the VQE execution",
)
serialized_graph: Dict[str, Any]
result
¶
AnglesResult (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/combinatorial_optimization/result.py
class AnglesResult(VersionedModel):
initial_point: List[float]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
PyomoObjectResult (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/combinatorial_optimization/result.py
class PyomoObjectResult(VersionedModel):
details: str
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
execution
special
¶
result
¶
ResourceEstimatorResult (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/execution/result.py
class ResourceEstimatorResult(VersionedModel):
report_json: str
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
executor
special
¶
aws_execution_cost
¶
ExecutionCostForTimePeriod (BaseModel)
pydantic-model
¶
Source code in classiq/interface/executor/aws_execution_cost.py
class ExecutionCostForTimePeriod(pydantic.BaseModel):
start: date = pydantic.Field(
description="The beginning of the time period for tasks usage and cost ("
"inclusive).",
)
end: date = pydantic.Field(
description="The end of the time period for tasks usage and cost (exclusive).",
)
granularity: Granularity = pydantic.Field(
description="Either MONTHLY or DAILY, or HOURLY.", default=Granularity.daily
)
cost_scope: CostScope = pydantic.Field(
description="Either user or organization", default=CostScope.user
)
class Config:
json_encoders = {date: lambda v: v.strftime("%Y-%m-%d")}
@validator("end")
def date_order(cls, v: date, values: Dict[str, Any], **kwargs: Any) -> date:
if "start" in values and v <= values["start"]:
raise ClassiqValueError('"end" date should be after "start" date')
return v
cost_scope: CostScope
pydantic-field
¶
Either user or organization
end: date
pydantic-field
required
¶
The end of the time period for tasks usage and cost (exclusive).
granularity: Granularity
pydantic-field
¶
Either MONTHLY or DAILY, or HOURLY.
start: date
pydantic-field
required
¶
The beginning of the time period for tasks usage and cost (inclusive).
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
estimation
¶
OperatorsEstimation (BaseModel)
pydantic-model
¶
Estimate the expectation value of a list of Pauli operators on a quantum state given by a quantum program.
Source code in classiq/interface/executor/estimation.py
class OperatorsEstimation(pydantic.BaseModel):
"""
Estimate the expectation value of a list of Pauli operators on a quantum state given
by a quantum program.
"""
quantum_program: QuantumCode
operators: PauliOperators
execution_preferences
¶
ExecutionPreferences (BaseModel)
pydantic-model
¶
Source code in classiq/interface/executor/execution_preferences.py
class ExecutionPreferences(pydantic.BaseModel):
timeout_sec: Optional[pydantic.PositiveInt] = pydantic.Field(
default=None,
description="If set, limits the execution runtime. Value is in seconds. "
"Not supported on all platforms.",
)
optimizer_preferences: Optional[OptimizerPreferences] = pydantic.Field(
default_factory=None,
description="Settings related to VQE execution.",
)
noise_properties: Optional[NoiseProperties] = pydantic.Field(
default=None, description="Properties of the noise in the circuit"
)
random_seed: int = pydantic.Field(
default=None,
description="The random seed used for the execution",
)
backend_preferences: BackendPreferencesTypes = backend_preferences_field(
backend_name=ClassiqSimulatorBackendNames.SIMULATOR
)
num_shots: Optional[pydantic.PositiveInt] = pydantic.Field(default=None)
transpile_to_hardware: TranspilationOption = pydantic.Field(
default=TranspilationOption.DECOMPOSE,
description="Transpile the circuit to the hardware basis gates before execution",
title="Transpilation Option",
)
job_name: Optional[str] = pydantic.Field(
min_length=1,
description="The job name",
)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
@pydantic.validator("num_shots", always=True)
def validate_num_shots(
cls, original_num_shots: Optional[pydantic.PositiveInt], values: Dict[str, Any]
) -> Optional[pydantic.PositiveInt]:
return _choose_original_or_optimizer_attribute(
original_num_shots, "num_shots", None, values
)
@pydantic.validator("backend_preferences", always=True)
def validate_timeout_for_aws(
cls, backend_preferences: BackendPreferencesTypes, values: Dict[str, Any]
) -> BackendPreferencesTypes:
timeout = values.get("timeout_sec", None)
if (
not isinstance(backend_preferences, AwsBackendPreferences)
or timeout is None
):
return backend_preferences
if (
timeout != backend_preferences.job_timeout
and backend_preferences.job_timeout != AWS_DEFAULT_JOB_TIMEOUT_SECONDS
):
raise ClassiqValueError(DIFFERENT_TIMEOUT_MSG)
if timeout > MAX_EXECUTION_TIMEOUT_SECONDS:
raise ClassiqValueError(TIMEOUT_LARGE_FOR_AWS_MSG)
backend_preferences.job_timeout = timeout
return backend_preferences
@pydantic.validator("random_seed", always=True)
def validate_random_seed(
cls, original_random_seed: Optional[int], values: Dict[str, Any]
) -> int:
return _choose_original_or_optimizer_attribute(
original_random_seed, "random_seed", create_random_seed(), values
)
backend_preferences: Union[classiq.interface.backend.backend_preferences.AzureBackendPreferences, classiq.interface.backend.backend_preferences.ClassiqBackendPreferences, classiq.interface.backend.backend_preferences.IBMBackendPreferences, classiq.interface.backend.backend_preferences.AwsBackendPreferences, classiq.interface.backend.backend_preferences.IonqBackendPreferences, classiq.interface.backend.backend_preferences.GCPBackendPreferences, classiq.interface.backend.backend_preferences.AliceBobBackendPreferences, classiq.interface.backend.backend_preferences.OQCBackendPreferences]
pydantic-field
¶
Preferences for the requested backend to run the quantum circuit.
job_name: ConstrainedStrValue
pydantic-field
¶
The job name
noise_properties: NoiseProperties
pydantic-field
¶
Properties of the noise in the circuit
optimizer_preferences: OptimizerPreferences
pydantic-field
¶
Settings related to VQE execution.
random_seed: int
pydantic-field
¶
The random seed used for the execution
timeout_sec: PositiveInt
pydantic-field
¶
If set, limits the execution runtime. Value is in seconds. Not supported on all platforms.
transpile_to_hardware: TranspilationOption
pydantic-field
¶
Transpile the circuit to the hardware basis gates before execution
execution_request
¶
ExecutionJobDetails (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/executor/execution_request.py
class ExecutionJobDetails(VersionedModel):
id: str
name: Optional[str]
start_time: datetime
end_time: Optional[datetime]
provider: Optional[str]
backend_name: Optional[str]
status: JobStatus
num_shots: Optional[int]
program_id: Optional[str]
error: Optional[str]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
ExecutionJobsQueryResults (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/executor/execution_request.py
class ExecutionJobsQueryResults(VersionedModel):
results: List[ExecutionJobDetails]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
ExecutionRequest (BaseModel)
pydantic-model
¶
Source code in classiq/interface/executor/execution_request.py
class ExecutionRequest(BaseModel, json_encoders=CUSTOM_ENCODERS):
execution_payload: ExecutionPayloads
preferences: ExecutionPreferences = pydantic.Field(
default_factory=ExecutionPreferences,
description="preferences for the execution",
)
QuantumProgramExecution (QuantumProgram)
pydantic-model
¶
Source code in classiq/interface/executor/execution_request.py
class QuantumProgramExecution(QuantumProgram):
execution_type: Literal["quantum_program2"] = "quantum_program2"
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
QuantumProgramExecutionRequest (ExecutionRequest)
pydantic-model
¶
Source code in classiq/interface/executor/execution_request.py
class QuantumProgramExecutionRequest(ExecutionRequest):
execution_payload: QuantumCodeExecution
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
execution_result
¶
ExecuteGeneratedCircuitResults (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/executor/execution_result.py
class ExecuteGeneratedCircuitResults(VersionedModel):
results: ResultsCollection
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
iqae_result
¶
IQAEResult (VersionedModel, QmodPyObject)
pydantic-model
¶
Source code in classiq/interface/executor/iqae_result.py
class IQAEResult(VersionedModel, QmodPyObject):
estimation: float
confidence_interval: Tuple[float, float]
iterations_data: List[IQAEIterationData]
warnings: List[str]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
optimizer_preferences
¶
CombinatorialOptimizer (OptimizerPreferences)
pydantic-model
¶
Source code in classiq/interface/executor/optimizer_preferences.py
class CombinatorialOptimizer(OptimizerPreferences):
cost_type: CostType = pydantic.Field(
default=CostType.CVAR,
description="Summarizing method of the measured bit strings",
)
alpha_cvar: Optional[PydanticAlphaParamCVAR] = pydantic.Field(
default=None, description="Parameter for the CVAR summarizing method"
)
is_maximization: bool = pydantic.Field(
default=False,
description="Whether the optimization goal is to maximize",
)
should_check_valid_solutions: bool = pydantic.Field(
default=False,
description="Whether to check if all the solutions satisfy the constraints",
)
@pydantic.validator("alpha_cvar", pre=True, always=True)
def check_alpha_cvar(
cls, alpha_cvar: Optional[PydanticAlphaParamCVAR], values: Dict[str, Any]
) -> Optional[PydanticAlphaParamCVAR]:
cost_type = values.get("cost_type")
if alpha_cvar is not None and cost_type != CostType.CVAR:
raise ClassiqValueError("Use CVAR params only for CostType.CVAR.")
if alpha_cvar is None and cost_type == CostType.CVAR:
alpha_cvar = PydanticAlphaParamCVAR(0.2)
return alpha_cvar
alpha_cvar: PydanticAlphaParamCVAR
pydantic-field
¶
Parameter for the CVAR summarizing method
cost_type: CostType
pydantic-field
¶
Summarizing method of the measured bit strings
is_maximization: bool
pydantic-field
¶
Whether the optimization goal is to maximize
should_check_valid_solutions: bool
pydantic-field
¶
Whether to check if all the solutions satisfy the constraints
OptimizerPreferences (BaseModel)
pydantic-model
¶
Source code in classiq/interface/executor/optimizer_preferences.py
class OptimizerPreferences(BaseModel):
name: OptimizerType = pydantic.Field(
default=OptimizerType.COBYLA, description="Classical optimization algorithm."
)
num_shots: Optional[pydantic.PositiveInt] = pydantic.Field(
default=None,
description="Number of repetitions of the quantum ansatz.",
)
max_iteration: pydantic.PositiveInt = pydantic.Field(
default=100, description="Maximal number of optimizer iterations"
)
tolerance: Optional[pydantic.PositiveFloat] = pydantic.Field(
default=None, description="Final accuracy in the optimization"
)
step_size: Optional[pydantic.PositiveFloat] = pydantic.Field(
default=None,
description="step size for numerically " "calculating the gradient",
)
random_seed: Optional[int] = pydantic.Field(
default=None,
description="The random seed used for the generation",
)
initial_point: Optional[List[float]] = pydantic.Field(
default=None,
description="Initial values for the ansatz parameters",
)
skip_compute_variance: bool = pydantic.Field(
default=False,
description="If True, the optimizer will not compute the variance of the ansatz.",
)
@pydantic.validator("tolerance", pre=True, always=True)
def check_tolerance(
cls, tolerance: Optional[pydantic.PositiveFloat], values: Dict[str, Any]
) -> Optional[pydantic.PositiveFloat]:
optimizer_type = values.get("type")
if tolerance is not None and optimizer_type == OptimizerType.SPSA:
raise ClassiqValueError("No tolerance param for SPSA optimizer")
if tolerance is None and optimizer_type != OptimizerType.SPSA:
tolerance = pydantic.PositiveFloat(0.001)
return tolerance
@pydantic.validator("step_size", pre=True, always=True)
def check_step_size(
cls, step_size: Optional[pydantic.PositiveFloat], values: Dict[str, Any]
) -> Optional[pydantic.PositiveFloat]:
optimizer_type = values.get("name")
if step_size is not None and optimizer_type not in (
OptimizerType.L_BFGS_B,
OptimizerType.ADAM,
):
raise ClassiqValueError(
"Use step_size only for L_BFGS_B or ADAM optimizers."
)
if step_size is None and optimizer_type in (
OptimizerType.L_BFGS_B,
OptimizerType.ADAM,
):
step_size = pydantic.PositiveFloat(0.05)
return step_size
initial_point: List[float]
pydantic-field
¶
Initial values for the ansatz parameters
max_iteration: PositiveInt
pydantic-field
¶
Maximal number of optimizer iterations
name: OptimizerType
pydantic-field
¶
Classical optimization algorithm.
num_shots: PositiveInt
pydantic-field
¶
Number of repetitions of the quantum ansatz.
random_seed: int
pydantic-field
¶
The random seed used for the generation
skip_compute_variance: bool
pydantic-field
¶
If True, the optimizer will not compute the variance of the ansatz.
step_size: PositiveFloat
pydantic-field
¶
step size for numerically calculating the gradient
tolerance: PositiveFloat
pydantic-field
¶
Final accuracy in the optimization
quantum_code
¶
QuantumBaseCode (BaseModel)
pydantic-model
¶
Source code in classiq/interface/executor/quantum_code.py
class QuantumBaseCode(BaseModel):
syntax: QuantumInstructionSet = pydantic.Field(
default=QuantumInstructionSet.QASM, description="The syntax of the program."
)
code: CodeType = pydantic.Field(
..., description="The textual representation of the program"
)
@pydantic.validator("code")
def load_quantum_program(
cls, code: Union[CodeType, IonqQuantumCircuit], values: Dict[str, Any]
) -> CodeType:
syntax = values.get("syntax")
if isinstance(code, IonqQuantumCircuit):
if syntax != QuantumInstructionSet.IONQ:
raise ClassiqValueError(
f"Invalid code type {type(code)} for syntax: {syntax}"
)
return code.json()
return code
QuantumCode (QuantumBaseCode)
pydantic-model
¶
Source code in classiq/interface/executor/quantum_code.py
class QuantumCode(QuantumBaseCode):
arguments: MultipleArguments = pydantic.Field(
default=(),
description="The parameters dictionary for a parametrized quantum program.",
)
output_qubits_map: OutputQubitsMap = pydantic.Field(
default_factory=dict,
description="The map of outputs to their qubits in the circuit.",
)
registers_initialization: Optional[RegistersInitialization] = pydantic.Field(
default_factory=None,
description="Initial conditions for the different registers in the circuit.",
)
synthesis_execution_data: Optional[ExecutionData] = pydantic.Field(default=None)
synthesis_execution_arguments: Arguments = pydantic.Field(default_factory=dict)
class Config:
validate_assignment = True
@pydantic.validator("arguments")
def validate_arguments(
cls, arguments: MultipleArguments, values: Dict[str, Any]
) -> MultipleArguments:
if arguments and values.get("syntax") not in (
QuantumInstructionSet.QSHARP,
QuantumInstructionSet.QASM,
):
raise ClassiqValueError("Only QASM or Q# programs support arguments")
if values.get("syntax") == QuantumInstructionSet.QSHARP and len(arguments) > 1:
raise ClassiqValueError(
f"Q# programs supports only one group of arguments. {len(arguments)} given"
)
return arguments
@pydantic.validator("synthesis_execution_data")
def validate_synthesis_execution_data(
cls,
synthesis_execution_data: Optional[ExecutionData],
values: Dict[str, Any],
) -> Optional[ExecutionData]:
if (
synthesis_execution_data is not None
and values.get("syntax") is not QuantumInstructionSet.QASM
):
raise ClassiqValueError("Only QASM supports the requested configuration")
return synthesis_execution_data
@staticmethod
def from_file(
file_path: Union[str, Path],
syntax: Optional[Union[str, QuantumInstructionSet]] = None,
arguments: MultipleArguments = (),
) -> QuantumCode:
path = Path(file_path)
code = path.read_text()
if syntax is None:
syntax = QuantumInstructionSet.from_suffix(path.suffix.lstrip("."))
return QuantumCode(syntax=syntax, code=code, arguments=arguments)
arguments: Tuple[Dict[classiq.interface.backend.pydantic_backend.ConstrainedStrValue, Any], ...]
pydantic-field
¶
The parameters dictionary for a parametrized quantum program.
output_qubits_map: Dict[str, Tuple[int, ...]]
pydantic-field
¶
The map of outputs to their qubits in the circuit.
registers_initialization: Dict[str, classiq.interface.executor.register_initialization.RegisterInitialization]
pydantic-field
¶
Initial conditions for the different registers in the circuit.
result
¶
EstimationResult (BaseModel, QmodPyObject)
pydantic-model
¶
Source code in classiq/interface/executor/result.py
class EstimationResult(BaseModel, QmodPyObject):
value: Complex = pydantic.Field(..., description="Estimation for the operator")
variance: Complex = pydantic.Field(..., description="Variance of the estimation")
metadata: EstimationMetadata = pydantic.Field(
..., description="Metadata for the estimation"
)
EstimationResults (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/executor/result.py
class EstimationResults(VersionedModel):
results: List[EstimationResult]
def __len__(self) -> int:
return len(self.results)
def __iter__(self) -> Iterator[EstimationResult]: # type: ignore[override]
# TODO This is a bug waiting to happen. We change the meaning of
# __iter__ in a derived class.
return iter(self.results)
def __getitem__(self, index: int) -> EstimationResult:
return self.results[index]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
ExecutionDetails (BaseModel, QmodPyObject)
pydantic-model
¶
Source code in classiq/interface/executor/result.py
class ExecutionDetails(BaseModel, QmodPyObject):
vendor_format_result: Dict[str, Any] = pydantic.Field(
..., description="Result in proprietary vendor format"
)
counts: Counts = pydantic.Field(
default_factory=dict, description="Number of counts per state"
)
counts_lsb_right: bool = pydantic.Field(
True,
description="Is the qubit order of counts field such that the LSB is right?",
)
parsed_states: ParsedStates = pydantic.Field(
default_factory=dict,
description="A mapping between the raw states of counts (bitstrings) to their parsed states (registers' values)",
)
histogram: Optional[Dict[State, pydantic.NonNegativeFloat]] = pydantic.Field(
None,
description="Histogram of probability per state (an alternative to counts)",
)
output_qubits_map: OutputQubitsMap = pydantic.Field(
default_factory=dict,
description="The map of outputs (measured registers) to their qubits in the circuit.",
)
state_vector: StateVector = pydantic.Field(
default=None,
description="The state vector when executed on a simulator, with LSB right qubit order",
)
parsed_state_vector_states: ParsedStates = pydantic.Field(
default=None,
description="A mapping between the raw states of the state vector (bitstrings) to their parsed states (registers' values)",
)
physical_qubits_map: Optional[OutputQubitsMap] = pydantic.Field(
default=None,
description="The map of all registers (also non measured) to their qubits in the circuit. Used for state_vector which represent also the non-measured qubits.",
)
num_shots: Optional[pydantic.NonNegativeInt] = pydantic.Field(
default=None, description="The total number of shots the circuit was executed"
)
@pydantic.validator("counts", pre=True)
def _clean_spaces_from_counts_keys(cls, v: Counts) -> Counts:
if not v or " " not in list(v.keys())[0]:
return v
return {state.replace(" ", ""): v[state] for state in v}
@pydantic.validator("num_shots", always=True)
def _validate_num_shots(
cls, num_shots: Optional[int], values: Dict[str, Any]
) -> Optional[int]:
if num_shots is not None:
return num_shots
counts = values.get("counts")
if not counts:
return None
return sum(shots for _, shots in counts.items())
@property
def parsed_counts(self) -> ParsedCounts:
return get_parsed_counts(self.counts, self.parsed_states)
@property
def parsed_state_vector(self) -> Optional[ParsedStateVector]:
if not self.state_vector:
return None
parsed_state_vector = [
SimulatedState(
state=self.parsed_state_vector_states[bitstring],
bitstring=bitstring,
amplitude=complex(amplitude_str),
)
for bitstring, amplitude_str in self.state_vector.items()
]
return sorted(parsed_state_vector, key=lambda k: abs(k.amplitude), reverse=True)
def flip_execution_counts_bitstring(self) -> None:
"""Backends should return result count bitstring in right to left form"""
self.counts = flip_counts_qubit_order(self.counts)
self.counts_lsb_right = not self.counts_lsb_right
def counts_by_qubit_order(self, lsb_right: bool) -> Counts:
if self.counts_lsb_right != lsb_right:
return flip_counts_qubit_order(self.counts)
else:
return self.counts
def counts_of_qubits(self, *qubits: int) -> Counts:
_validate_qubit_indices(self.counts, qubits)
reduced_counts: DefaultDict[State, int] = defaultdict(int)
for state_str, state_count in self.counts_by_qubit_order(
lsb_right=False
).items():
reduced_counts[_slice_str(state_str, qubits)] += state_count
return dict(reduced_counts)
def counts_of_output(self, output_name: Name) -> Counts:
if output_name not in self.output_qubits_map:
raise ClassiqError(_UNAVAILABLE_OUTPUT_ERROR_MSG)
return self.counts_of_qubits(*self.output_qubits_map[output_name])
def counts_of_multiple_outputs(
self, output_names: Tuple[Name, ...]
) -> Dict[Tuple[State, ...], pydantic.NonNegativeInt]:
if any(name not in self.output_qubits_map for name in output_names):
raise ClassiqError(_UNAVAILABLE_OUTPUT_ERROR_MSG)
output_regs: Tuple[Qubits, ...] = tuple(
self.output_qubits_map[name] for name in output_names
)
reduced_counts: DefaultDict[Tuple[State, ...], int] = defaultdict(int)
for state_str, state_count in self.counts_by_qubit_order(
lsb_right=False
).items():
reduced_strs = tuple(_slice_str(state_str, reg) for reg in output_regs)
reduced_counts[reduced_strs] += state_count
return dict(reduced_counts)
def parsed_counts_of_outputs(
self, output_names: Union[Name, Tuple[Name, ...]]
) -> ParsedCounts:
if isinstance(output_names, Name):
output_names = (output_names,)
if any(name not in self.output_qubits_map for name in output_names):
raise ClassiqError(_UNAVAILABLE_OUTPUT_ERROR_MSG)
reduced_parsed_states = reduce_parsed_states(self.parsed_states, output_names)
return get_parsed_counts(self.counts, reduced_parsed_states)
def register_output_from_qubits(self, qubits: Tuple[int, ...]) -> Dict[float, int]:
register_output: Dict[float, int] = {}
value_from_str_bin = functools.partial(
self._get_register_value_from_binary_string_results, register_qubits=qubits
)
for results_binary_key, counts in self.counts_by_qubit_order(
lsb_right=False
).items():
value = value_from_str_bin(binary_string=results_binary_key)
register_output[value] = register_output.get(value, 0) + counts
return register_output
@staticmethod
def _get_register_value_from_binary_string_results(
binary_string: str, register_qubits: List[int]
) -> RegisterValue:
register_binary_string = "".join(
operator.itemgetter(*register_qubits)(binary_string)
)[::-1]
return number_utils.binary_to_float_or_int(bin_rep=register_binary_string)
counts: Dict[str, pydantic.types.NonNegativeInt]
pydantic-field
¶
Number of counts per state
counts_lsb_right: bool
pydantic-field
¶
Is the qubit order of counts field such that the LSB is right?
histogram: Dict[str, pydantic.types.NonNegativeFloat]
pydantic-field
¶
Histogram of probability per state (an alternative to counts)
num_shots: NonNegativeInt
pydantic-field
¶
The total number of shots the circuit was executed
output_qubits_map: Dict[str, Tuple[int, ...]]
pydantic-field
¶
The map of outputs (measured registers) to their qubits in the circuit.
parsed_state_vector_states: Mapping[str, Mapping[str, Union[float, int]]]
pydantic-field
¶
A mapping between the raw states of the state vector (bitstrings) to their parsed states (registers' values)
parsed_states: Mapping[str, Mapping[str, Union[float, int]]]
pydantic-field
¶
A mapping between the raw states of counts (bitstrings) to their parsed states (registers' values)
physical_qubits_map: Dict[str, Tuple[int, ...]]
pydantic-field
¶
The map of all registers (also non measured) to their qubits in the circuit. Used for state_vector which represent also the non-measured qubits.
state_vector: Dict[str, Any]
pydantic-field
¶
The state vector when executed on a simulator, with LSB right qubit order
vendor_format_result: Dict[str, Any]
pydantic-field
required
¶
Result in proprietary vendor format
flip_execution_counts_bitstring(self)
¶
Backends should return result count bitstring in right to left form
Source code in classiq/interface/executor/result.py
def flip_execution_counts_bitstring(self) -> None:
"""Backends should return result count bitstring in right to left form"""
self.counts = flip_counts_qubit_order(self.counts)
self.counts_lsb_right = not self.counts_lsb_right
GroverSimulationResults (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/executor/result.py
class GroverSimulationResults(VersionedModel):
result: Dict[str, Any]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
MultipleExecutionDetails (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/executor/result.py
class MultipleExecutionDetails(VersionedModel):
details: List[ExecutionDetails]
def __getitem__(self, index: int) -> ExecutionDetails:
return self.details[index]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
SampledState (BaseModel)
pydantic-model
¶
Source code in classiq/interface/executor/result.py
class SampledState(BaseModel):
state: ParsedState
shots: MeasuredShots
def __repr__(self) -> str:
return f"{self.state}: {self.shots}"
__repr__(self)
special
¶
Return repr(self).
Source code in classiq/interface/executor/result.py
def __repr__(self) -> str:
return f"{self.state}: {self.shots}"
vqe_result
¶
VQEIntermediateData (BaseModel)
pydantic-model
¶
Source code in classiq/interface/executor/vqe_result.py
class VQEIntermediateData(BaseModel):
utc_time: datetime = pydantic.Field(description="Time when the iteration finished")
iteration_number: pydantic.PositiveInt = pydantic.Field(
description="The iteration's number (evaluation count)"
)
parameters: List[float] = pydantic.Field(
description="The optimizer parameters for the variational form"
)
mean_all_solutions: Optional[float] = pydantic.Field(
default=None, description="The mean score of all solutions in this iteration"
)
solutions: List[SolutionData] = pydantic.Field(
description="Solutions found in this iteration, their score and"
"number of repetitions"
)
standard_deviation: float = pydantic.Field(
description="The evaluated standard deviation"
)
iteration_number: PositiveInt
pydantic-field
required
¶
The iteration's number (evaluation count)
mean_all_solutions: float
pydantic-field
¶
The mean score of all solutions in this iteration
parameters: List[float]
pydantic-field
required
¶
The optimizer parameters for the variational form
solutions: List[classiq.interface.executor.vqe_result.SolutionData]
pydantic-field
required
¶
Solutions found in this iteration, their score andnumber of repetitions
standard_deviation: float
pydantic-field
required
¶
The evaluated standard deviation
utc_time: datetime
pydantic-field
required
¶
Time when the iteration finished
finance
special
¶
finance_modelling_params
¶
FinanceModellingParams (BaseModel)
pydantic-model
¶
Source code in classiq/interface/finance/finance_modelling_params.py
class FinanceModellingParams(BaseModel):
finance_model: Finance = pydantic.Field(
description="The model parameter for the finance problem."
)
phase_port_size: int = pydantic.Field(description="Width of the phase port.")
function_input
¶
FinanceFunctionInput (BaseModel)
pydantic-model
¶
Source code in classiq/interface/finance/function_input.py
class FinanceFunctionInput(pydantic.BaseModel):
f: FinanceFunctionType = pydantic.Field(
description="An enumeration of the wanted financial function: VaR, expected "
"shortfall, European call options or x^2"
)
variable: str = pydantic.Field(
default="x", description="Variable/s of the function"
)
condition: FunctionCondition = pydantic.Field(
description="The condition for the function"
)
polynomial_degree: Optional[int] = pydantic.Field(
default=None,
description="The polynomial degree of approximation, uses linear approximation by default",
)
use_chebyshev_polynomial_approximation: bool = pydantic.Field(
default=False,
description="Flag if to use chebyshev polynomial approximation for target function",
)
tail_probability: Optional[PydanticNonZeroProbabilityFloat] = pydantic.Field(
default=None,
description="The required probability on the tail of the distribution (1 - percentile)",
)
@pydantic.validator("f", pre=True)
def _convert_f_if_str(cls, f: Any) -> FinanceFunctionType:
# Keep this for backwards-compatible string support
if f in FINANCE_FUNCTION_STRING:
return FINANCE_FUNCTION_STRING[f]
return f
@pydantic.validator("use_chebyshev_polynomial_approximation")
def _validate_polynomial_flag(
cls, use_chebyshev_flag: bool, values: Dict[str, Any]
) -> bool:
if use_chebyshev_flag ^ (values.get("polynomial_degree") is None):
return use_chebyshev_flag
raise ClassiqValueError(
"Degree must be positive and use_chebyshev_polynomial_approximation set to True"
)
@pydantic.validator("f")
def _validate_finance_function(
cls, f: Union[int, str, FinanceFunctionType]
) -> FinanceFunctionType:
if isinstance(f, FinanceFunctionType):
return f
if isinstance(f, int):
return FinanceFunctionType(f)
return FinanceFunctionType.from_string(f)
@pydantic.validator("tail_probability", always=True)
def _validate_tail_probability_assignment_for_shortfall(
cls,
tail_probability: Optional[PydanticNonZeroProbabilityFloat],
values: Dict[str, Any],
) -> Optional[PydanticNonZeroProbabilityFloat]:
if values.get("f") == FinanceFunctionType.SHORTFALL and not tail_probability:
raise ClassiqValueError(
"Tail probability must be set for expected shortfall"
)
return tail_probability
class Config:
frozen = True
condition: FunctionCondition
pydantic-field
required
¶
The condition for the function
f: FinanceFunctionType
pydantic-field
required
¶
An enumeration of the wanted financial function: VaR, expected shortfall, European call options or x^2
polynomial_degree: int
pydantic-field
¶
The polynomial degree of approximation, uses linear approximation by default
tail_probability: PydanticNonZeroProbabilityFloat
pydantic-field
¶
The required probability on the tail of the distribution (1 - percentile)
use_chebyshev_polynomial_approximation: bool
pydantic-field
¶
Flag if to use chebyshev polynomial approximation for target function
variable: str
pydantic-field
¶
Variable/s of the function
FunctionCondition (BaseModel)
pydantic-model
¶
Source code in classiq/interface/finance/function_input.py
class FunctionCondition(pydantic.BaseModel):
threshold: float
larger: bool = pydantic.Field(
default=False,
description="When true, function is set when input is larger to threshold and otherwise 0. Default is False.",
)
class Config:
frozen = True
larger: bool
pydantic-field
¶
When true, function is set when input is larger to threshold and otherwise 0. Default is False.
gaussian_model_input
¶
GaussianModelInput (FinanceModelInput)
pydantic-model
¶
Source code in classiq/interface/finance/gaussian_model_input.py
class GaussianModelInput(FinanceModelInput):
kind: Literal["gaussian"] = pydantic.Field(default="gaussian")
num_qubits: pydantic.PositiveInt = pydantic.Field(
description="The number of qubits represent"
"the latent normal random variable Z (Resolution of "
"the random variable Z)."
)
normal_max_value: float = pydantic.Field(
description="Min/max value to truncate the " "latent normal random variable Z"
)
default_probabilities: List[PydanticProbabilityFloat] = pydantic.Field(
description="default probabilities for each asset"
)
rhos: List[pydantic.PositiveFloat] = pydantic.Field(
description="Sensitivities of default probability of assets "
"with respect to Z (1/sigma(Z))"
)
loss: List[int] = pydantic.Field(
description="List of ints signifying loss per asset"
)
min_loss: Optional[int] = pydantic.Field(
description="Minimum possible loss for the model "
)
@property
def num_model_qubits(self) -> int:
return len(self.rhos)
@property
def distribution_range(self) -> Tuple[float, float]:
return 0, sum(self.loss)
@property
def num_output_qubits(self) -> int:
return int(math.log2(sum(self.loss))) + 1
@property
def num_bernoulli_qubits(self) -> int:
return self.num_qubits + self.num_model_qubits
default_probabilities: List[classiq.interface.helpers.custom_pydantic_types.PydanticProbabilityFloat]
pydantic-field
required
¶
default probabilities for each asset
loss: List[int]
pydantic-field
required
¶
List of ints signifying loss per asset
min_loss: int
pydantic-field
¶
Minimum possible loss for the model
normal_max_value: float
pydantic-field
required
¶
Min/max value to truncate the latent normal random variable Z
num_qubits: PositiveInt
pydantic-field
required
¶
The number of qubits representthe latent normal random variable Z (Resolution of the random variable Z).
rhos: List[pydantic.types.PositiveFloat]
pydantic-field
required
¶
Sensitivities of default probability of assets with respect to Z (1/sigma(Z))
log_normal_model_input
¶
LogNormalModelInput (FinanceModelInput)
pydantic-model
¶
Source code in classiq/interface/finance/log_normal_model_input.py
class LogNormalModelInput(FinanceModelInput):
kind: Literal["log_normal"] = pydantic.Field(default="log_normal")
num_qubits: pydantic.PositiveInt = pydantic.Field(
description="Number of qubits to represent the probability."
)
mu: pydantic.NonNegativeFloat = pydantic.Field(
description="Mean of the Normal distribution variable X s.t. ln(X) ~ log-normal."
)
sigma: pydantic.PositiveFloat = pydantic.Field(
description="Std of the Normal distribution variable X s.t. ln(X) ~ log-normal."
)
@property
def distribution_range(self) -> Tuple[float, float]:
mean = np.exp(self.mu + self.sigma**2 / 2)
variance = (np.exp(self.sigma**2) - 1) * np.exp(2 * self.mu + self.sigma**2)
stddev = np.sqrt(variance)
low = np.maximum(0, mean - 3 * stddev)
high = mean + 3 * stddev
return low, high
@property
def num_model_qubits(self) -> int:
return self.num_qubits
@property
def num_output_qubits(self) -> int:
return self.num_qubits
class Config:
frozen = True
mu: NonNegativeFloat
pydantic-field
required
¶
Mean of the Normal distribution variable X s.t. ln(X) ~ log-normal.
num_qubits: PositiveInt
pydantic-field
required
¶
Number of qubits to represent the probability.
sigma: PositiveFloat
pydantic-field
required
¶
Std of the Normal distribution variable X s.t. ln(X) ~ log-normal.
generator
special
¶
amplitude_estimation
¶
AmplitudeEstimation (FunctionParams)
pydantic-model
¶
Creates a quantum circuit for amplitude estimation Provide the state preparation and oracle within the GroverOperator parameter Choose estimation accuracy with the estimation_register_size parameter
Source code in classiq/interface/generator/amplitude_estimation.py
class AmplitudeEstimation(FunctionParams):
"""
Creates a quantum circuit for amplitude estimation
Provide the state preparation and oracle within the GroverOperator parameter
Choose estimation accuracy with the estimation_register_size parameter
"""
grover_operator: GroverOperator = pydantic.Field(
description="The Grover Operator used in the algorithm. "
"Composed of the oracle and the state preparation operator."
)
estimation_register_size: pydantic.PositiveInt = pydantic.Field(
description="The number of qubits used to estimate the amplitude. "
"Bigger register provides a better estimate of the good states' amplitude."
)
def _create_ios(self) -> None:
self._inputs = dict()
self._outputs = {
ESTIMATED_AMPLITUDE_OUTPUT_NAME: RegisterArithmeticInfo(
size=self.estimation_register_size
),
**self.grover_operator.outputs,
}
estimation_register_size: PositiveInt
pydantic-field
required
¶
The number of qubits used to estimate the amplitude. Bigger register provides a better estimate of the good states' amplitude.
grover_operator: GroverOperator
pydantic-field
required
¶
The Grover Operator used in the algorithm. Composed of the oracle and the state preparation operator.
amplitude_loading
¶
AmplitudeLoading (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/amplitude_loading.py
class AmplitudeLoading(FunctionParams):
size: pydantic.PositiveInt = pydantic.Field(
description="The number of qubits of the amplitude input."
)
expression: PydanticExpressionStr = pydantic.Field(
description="The mathematical expression of the amplitude loading function."
)
implementation: AmplitudeLoadingImplementation = pydantic.Field(
default=AmplitudeLoadingImplementation.EXPERIMENTAL,
description="Implementation options.",
)
@pydantic.validator("expression", pre=True)
def validate_coefficient(cls, expression: str) -> str:
if isinstance(expression, str):
# We validate the given value is legal and does not contain code that will be executed in our BE.
validate_expression(
expression,
supported_nodes=get_args(GenerationExpressionSupportedNodeTypes),
)
# We only check that this method does not raise any exception to see that it can be converted to sympy
sympy.parse_expr(expression)
if isinstance(expression, sympy.Expr):
return str(expression)
return expression
@pydantic.root_validator()
def check_all_variable_are_defined(cls, values: Dict[str, Any]) -> Dict[str, Any]:
expression = values.get("expression", "")
literals = set(re.findall(SUPPORTED_VAR_NAMES_REG, expression))
not_allowed = literals.intersection(FORBIDDEN_LITERALS)
variables = literals.difference(SUPPORTED_FUNC_NAMES)
if not_allowed:
raise ClassiqValueError(
f"The following names: {not_allowed} are not allowed"
)
if len(variables) != 1:
raise ClassiqValueError(f"{variables} must contain exactly single variable")
return values
def _create_ios(self) -> None:
self._inputs = {
TARGET_OUTPUT_NAME: RegisterUserInput(name=TARGET_OUTPUT_NAME, size=1),
AMPLITUDE_IO_NAME: RegisterUserInput(
name=AMPLITUDE_IO_NAME, size=self.size
),
}
self._outputs = {
TARGET_OUTPUT_NAME: RegisterUserInput(name=TARGET_OUTPUT_NAME, size=1),
**self._inputs,
}
@property
def variable(self) -> str:
literals = set(re.findall(SUPPORTED_VAR_NAMES_REG, self.expression))
return list(literals.difference(SUPPORTED_FUNC_NAMES))[0]
arith
special
¶
arithmetic_expression_validator
¶
ExpressionValidator (NodeVisitor)
¶
Source code in classiq/interface/generator/arith/arithmetic_expression_validator.py
class ExpressionValidator(ast.NodeVisitor):
def __init__(
self,
supported_nodes: Tuple[Type[AST], ...],
expression_type: str = DEFAULT_EXPRESSION_TYPE,
supported_functions: Optional[Set[str]] = None,
supported_attr_values: Optional[Set[str]] = None,
mode: str = "eval",
) -> None:
super().__init__()
self.supported_nodes = supported_nodes
self._expression_type = expression_type
self._supported_functions = supported_functions or DEFAULT_SUPPORTED_FUNC_NAMES
self._supported_attr_values = supported_attr_values or set()
self._mode = mode
self._ast_obj: Optional[ast.AST] = None
def validate(self, expression: str) -> None:
try:
adjusted_expression = self._get_adjusted_expression(expression)
ast_expr = ast.parse(adjusted_expression, filename="", mode=self._mode)
except SyntaxError as e:
raise ClassiqValueError(f"Failed to parse expression {expression!r}") from e
try:
self._ast_obj = self.rewrite_ast(ast_expr)
self.visit(self._ast_obj)
except RecursionError as e:
raise ClassiqValueError(
f"Failed to parse expression since it is too long: {expression}"
) from e
@staticmethod
def _get_adjusted_expression(expression: str) -> str:
# This works around the simplification of the trivial expressions such as a + 0, 1 * a, etc.
if IDENITIFIER_REGEX.fullmatch(expression):
return f"0 + {expression}"
return expression
@property
def ast_obj(self) -> ast.AST:
if not self._ast_obj:
raise ClassiqArithmeticError("Must call `validate` before getting ast_obj")
return self._ast_obj
@staticmethod
def _check_repeated_variables(variables: Tuple[Any, Any]) -> None:
if (
all(isinstance(var, ast.Name) for var in variables)
and variables[0].id == variables[1].id
):
raise ClassiqValueError(_REPEATED_VARIABLES_ERROR_MESSAGE)
@staticmethod
def _check_multiple_comparators(node: ast.Compare) -> None:
if len(node.comparators) > 1:
raise ClassiqValueError(
"Arithmetic expression with more than 1 comparator is not supported"
)
def generic_visit(self, node: ast.AST) -> None:
self._validate_node_type(node)
return super().generic_visit(node)
def _validate_node_type(self, node: ast.AST) -> None:
if isinstance(node, self.supported_nodes):
return
raise ClassiqValueError(
f"Invalid {self._expression_type} expression: "
f"{type(node).__name__} is not supported"
)
def validate_Compare(self, node: ast.Compare) -> None: # noqa: N802
self._check_repeated_variables((node.left, node.comparators[0]))
self._check_multiple_comparators(node)
def visit_Compare(self, node: ast.Compare) -> None:
self.validate_Compare(node)
self.generic_visit(node)
def validate_BinOp(self, node: ast.BinOp) -> None: # noqa: N802
self._check_repeated_variables((node.left, node.right))
def visit_BinOp(self, node: ast.BinOp) -> None:
self.validate_BinOp(node)
self.generic_visit(node)
def validate_Call(self, node: ast.Call) -> None: # noqa: N802
if len(node.args) >= 2:
self._check_repeated_variables((node.args[0], node.args[1]))
node_id = AstNodeRewrite().extract_node_id(node)
if node_id not in self._supported_functions:
raise ClassiqValueError(f"{node_id} not in supported functions")
if node_id in ("CLShift", "CRShift") and (
len(node.args) != 2 or not isinstance(node.args[1], ast.Constant)
):
raise ClassiqValueError("Cyclic Shift expects 2 arguments (exp, int)")
def visit_Call(self, node: ast.Call) -> None:
self.validate_Call(node)
self.generic_visit(node)
def validate_Constant(self, node: ast.Constant) -> None: # noqa: N802
if not isinstance(node.value, (int, float, complex, str)):
raise ClassiqValueError(
f"{type(node.value).__name__} literals are not valid in {self._expression_type} expressions"
)
def visit_Constant(self, node: ast.Constant) -> None:
self.validate_Constant(node)
self.generic_visit(node)
def validate_Attribute(self, node: ast.Attribute) -> None: # noqa: N802
if not (
isinstance(node.value, ast.Name)
and node.value.id in self._supported_attr_values
or node.attr in CLASSICAL_ATTRIBUTES.keys()
):
raise ClassiqValueError(
f"Attribute is not supported for value {node.value}"
)
def visit_Attribute(self, node: ast.Attribute) -> None:
self.validate_Attribute(node)
self.generic_visit(node)
def visit_FunctionDef(self, node: ast.FunctionDef) -> Any:
if self._mode == "exec":
if hasattr(builtins, node.name):
raise ClassiqValueError(
f"Defining a function named {node.name} is forbidden"
)
self._supported_functions.add(node.name)
self.generic_visit(node)
@classmethod
def rewrite_ast(cls, expression_ast: AST) -> AST:
return expression_ast
generic_visit(self, node)
¶Called if no explicit visitor function exists for a node.
Source code in classiq/interface/generator/arith/arithmetic_expression_validator.py
def generic_visit(self, node: ast.AST) -> None:
self._validate_node_type(node)
return super().generic_visit(node)
ast_node_rewrite
¶
AstNodeRewrite (NodeTransformer)
¶
Source code in classiq/interface/generator/arith/ast_node_rewrite.py
class AstNodeRewrite(ast.NodeTransformer):
def __init__(self) -> None:
super().__init__()
self.count_str_gen = _count_str_gen()
def visit(self, node: ast.AST) -> ast.AST:
new_node = ast.NodeTransformer.visit(self, node=node)
new_node.id = self.extract_node_id(new_node)
return new_node
def extract_node_id(self, node: ast.AST) -> Optional[Union[str, float]]:
if hasattr(node, "id"):
return node.id
elif hasattr(node, "op"):
return type(node.op).__name__ + next(self.count_str_gen)
elif hasattr(node, "func"):
return self.extract_node_id(node.func)
elif hasattr(node, "value"):
return node.value
elif hasattr(node, "ops"):
return type(node.ops[0]).__name__ + next(self.count_str_gen)
return None
def visit_UnaryOp(self, node: ast.UnaryOp) -> Any:
if hasattr(node, OUTPUT_SIZE):
node.operand.output_size = node.output_size # type: ignore[attr-defined]
node = cast(ast.UnaryOp, self.generic_visit(node))
if isinstance(node.op, ast.UAdd):
return node.operand
elif isinstance(node.op, ast.USub) and isinstance(node.operand, ast.Constant):
return self.visit(ast.Constant(value=-node.operand.value))
return node
def visit_BinOp(self, node: ast.BinOp) -> Any:
if hasattr(node, OUTPUT_SIZE):
node.left.output_size = node.output_size # type: ignore[attr-defined]
node.right.output_size = node.output_size # type: ignore[attr-defined]
node = cast(ast.BinOp, self.generic_visit(node))
if isinstance(node.op, ast.Mod):
if not isinstance(node.right, ast.Constant) or isinstance(
node.left, ast.Constant
):
raise ClassiqArithmeticError(
"Modulo must be between a variable and a constant"
)
value = node.right.value
is_power_2 = value > 0 and (value & (value - 1) == 0)
if not is_power_2:
raise ClassiqArithmeticError(NOT_POWER_OF_TWO_ERROR_MSG)
if not isinstance(node.left, ast.Name):
node.left.output_size = node.right.value.bit_length() - 1 # type: ignore[attr-defined]
return node.left
return node
visit(self, node)
¶Visit a node.
Source code in classiq/interface/generator/arith/ast_node_rewrite.py
def visit(self, node: ast.AST) -> ast.AST:
new_node = ast.NodeTransformer.visit(self, node=node)
new_node.id = self.extract_node_id(new_node)
return new_node
number_utils
¶
signed_int_to_unsigned(number)
¶
Return the integer value of a signed int if it would we read as un-signed in binary representation
Source code in classiq/interface/generator/arith/number_utils.py
def signed_int_to_unsigned(number: int) -> int:
"""Return the integer value of a signed int if it would we read as un-signed in binary representation"""
if number >= 0:
return number
not_power2 = abs(number) & (abs(number) - 1) != 0
return number + 2 ** (number.bit_length() + 1 * not_power2)
chemistry_function_params
¶
ChemistryFunctionParams (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/chemistry_function_params.py
class ChemistryFunctionParams(FunctionParams):
gs_problem: CHEMISTRY_PROBLEMS_TYPE
@pydantic.validator("gs_problem")
def validate_gs_problem_contains_num_qubits(
cls, gs_problem: CHEMISTRY_PROBLEMS_TYPE
) -> CHEMISTRY_PROBLEMS_TYPE:
if not gs_problem.num_qubits:
raise ClassiqValueError(
"Ground state problem doesn't contain num_qubits. "
"Use update_problem method."
)
return gs_problem
@property
def num_qubits(self) -> int:
assert isinstance(
self.gs_problem, GroundStateProblem
), "self.gs_problem is not from GroundStateProblem class"
assert isinstance(self.gs_problem.num_qubits, int)
return self.gs_problem.num_qubits
def _create_ios(self) -> None:
self._inputs = {
DEFAULT_INPUT_NAME: RegisterUserInput(
name=DEFAULT_INPUT_NAME, size=self.num_qubits
)
}
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterUserInput(
name=DEFAULT_OUTPUT_NAME, size=self.num_qubits
)
}
gs_problem: Annotated[Union[classiq.interface.chemistry.ground_state_problem.MoleculeProblem, classiq.interface.chemistry.ground_state_problem.HamiltonianProblem], FieldInfo(default=PydanticUndefined, description='Ground state problem object describing the system.', discriminator='kind', extra={})]
pydantic-field
required
¶
Ground state problem object describing the system.
commuting_pauli_exponentiation
¶
CommutingPauliExponentiation (FunctionParams)
pydantic-model
¶
Exponentiation of a Hermitian Pauli sum operator with commuting pauli strings.
Source code in classiq/interface/generator/commuting_pauli_exponentiation.py
class CommutingPauliExponentiation(FunctionParams):
"""
Exponentiation of a Hermitian Pauli sum operator with commuting pauli strings.
"""
pauli_operator: PauliOperator = pydantic.Field(
description="A weighted sum of Pauli strings."
)
evolution_coefficient: ParameterFloatType = pydantic.Field(
default=1.0,
description="A global coefficient multiplying the operator.",
is_exec_param=True,
)
@pydantic.validator("pauli_operator")
def _validate_is_hermitian(cls, pauli_operator: PauliOperator) -> PauliOperator:
return operator.validate_operator_is_hermitian(pauli_operator)
@pydantic.validator("pauli_operator")
def _validate_paulis_commute(cls, pauli_operator: PauliOperator) -> PauliOperator:
if not pauli_operator.is_commutative:
raise ClassiqValueError("Pauli strings are not commutative")
return pauli_operator
def _create_ios(self) -> None:
size = self.pauli_operator.num_qubits
self._inputs = {
DEFAULT_INPUT_NAME: RegisterUserInput(name=DEFAULT_INPUT_NAME, size=size)
}
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterUserInput(name=DEFAULT_OUTPUT_NAME, size=size)
}
control_state
¶
ControlState (BaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/control_state.py
class ControlState(BaseModel):
num_ctrl_qubits: pydantic.PositiveInt = pydantic.Field(
default=_DEFAULT_NUM_CONTROL_QUBITS, description="Number of control qubits"
)
ctrl_state: str = pydantic.Field(
default=_INVALID_CONTROL_STATE, description="Control state string"
)
name: str = pydantic.Field(default=None, description="Control name")
@pydantic.root_validator()
def _validate_control(cls, values: Dict[str, Any]) -> Dict[str, Any]:
num_ctrl_qubits: int = values.get(
"num_ctrl_qubits", _DEFAULT_NUM_CONTROL_QUBITS
)
ctrl_state: str = values.get("ctrl_state", _INVALID_CONTROL_STATE)
if ctrl_state == _INVALID_CONTROL_STATE:
ctrl_state = "1" * num_ctrl_qubits
values["ctrl_state"] = ctrl_state
cls.validate_control_string(ctrl_state)
if num_ctrl_qubits == _DEFAULT_NUM_CONTROL_QUBITS:
num_ctrl_qubits = len(ctrl_state)
values["num_ctrl_qubits"] = num_ctrl_qubits
if len(ctrl_state) != num_ctrl_qubits:
raise ClassiqValueError(
"Control state length should be equal to the number of control qubits"
)
if values.get("name") is None:
values["name"] = f"{_DEFAULT_CONTROL_NAME}_{ctrl_state}"
return values
@staticmethod
def validate_control_string(ctrl_state: str) -> None:
if not set(ctrl_state) <= {"1", "0"}:
raise ClassiqValueError(
f"Control state can only be constructed from 0 and 1, received: {ctrl_state}"
)
if not ctrl_state:
raise ClassiqValueError("Control state cannot be empty")
def __str__(self) -> str:
return self.ctrl_state
def __len__(self) -> int:
return self.num_ctrl_qubits
@property
def control_register(self) -> RegisterUserInput:
return RegisterUserInput(name=self.name, size=self.num_ctrl_qubits)
def rename(self, name: str) -> ControlState:
return ControlState(ctrl_state=self.ctrl_state, name=name)
class Config:
frozen = True
ctrl_state: str
pydantic-field
¶
Control state string
name: str
pydantic-field
¶
Control name
num_ctrl_qubits: PositiveInt
pydantic-field
¶
Number of control qubits
__str__(self)
special
¶
Return str(self).
Source code in classiq/interface/generator/control_state.py
def __str__(self) -> str:
return self.ctrl_state
credit_risk_example
special
¶
linear_gci
¶
LinearGCI (FunctionParams)
pydantic-model
¶
A circuit composed of a series of Y rotations to perform a linear approximation to the Gaussian Conditional Independence model. The model consists of a Bernoulli probability, which itself is a function of a latent random variable x: p(x) = F( (F_inv(p_zero) + x*sqrt(rho))/sqrt(1-rho) ) with F being the Gaussian CDF, p_zero is p(x=0) when rho=0, and rho reflects the sensitivity.
The circuit takes a state register |x> and zero-input target qubits. Each target qubit undergoes the following transformation: |x>|0> -> cos((slopex + offset)/2)|x>|0> + sin((slopex + offset)/2)|x>|1> Where the slope and the offset are determined by the linear approximation of the inverse sine of p(x).
Source code in classiq/interface/generator/credit_risk_example/linear_gci.py
class LinearGCI(function_params.FunctionParams):
"""
A circuit composed of a series of Y rotations to perform a linear approximation to the Gaussian Conditional
Independence model.
The model consists of a Bernoulli probability, which itself is a function of a latent random variable x:
p(x) = F( (F_inv(p_zero) + x*sqrt(rho))/sqrt(1-rho) )
with F being the Gaussian CDF, p_zero is p(x=0) when rho=0, and rho reflects the sensitivity.
The circuit takes a state register |x> and zero-input target qubits.
Each target qubit undergoes the following transformation:
|x>|0> -> cos((slope*x + offset)/2)|x>|0> + sin((slope*x + offset)/2)|x>|1>
Where the slope and the offset are determined by the linear approximation of the inverse sine of p(x).
"""
num_state_qubits: pydantic.PositiveInt = pydantic.Field(
description="The number of input qubits"
)
truncation_value: float = pydantic.Field(
description="The truncation value of the latent normal distribution"
)
p_zeros: List[PydanticProbabilityFloat] = pydantic.Field(
description="The probability when the latent normal variable equals zero"
)
rhos: List[PydanticNonOneProbabilityFloat] = pydantic.Field(
description="The sensitivity of the probability to changes in the latent normal variable values"
)
@pydantic.validator("rhos")
def validate_rhos(
cls, rhos: List[PydanticNonOneProbabilityFloat], values: Dict[str, Any]
) -> List[PydanticNonOneProbabilityFloat]:
p_zeros = values.get("p_zeros")
if p_zeros is None:
raise ClassiqValueError("Cannot validate rhos, p_zeros is missing")
if len(p_zeros) != len(rhos):
raise ClassiqValueError(RHOS_PZEROS_LENGTH_ERROR_MSG)
return rhos
@property
def linear_pauli_rotations(self) -> linear_pauli_rotations.LinearPauliRotations:
offsets, slopes = get_gci_values(
qubit_count_state=self.num_state_qubits,
truncation_value=self.truncation_value,
rhos=self.rhos,
p_zeros=self.p_zeros,
)
return linear_pauli_rotations.LinearPauliRotations(
num_state_qubits=self.num_state_qubits,
bases=["Y"] * len(self.rhos),
offsets=offsets,
slopes=slopes,
)
def _create_ios(self) -> None:
state_name = linear_pauli_rotations.STATE
target_name = linear_pauli_rotations.TARGET
self._inputs = {
state_name: RegisterUserInput(name=state_name, size=self.num_state_qubits),
target_name: RegisterUserInput(name=target_name, size=len(self.rhos)),
}
self._outputs = {**self.inputs}
num_state_qubits: PositiveInt
pydantic-field
required
¶The number of input qubits
p_zeros: List[classiq.interface.helpers.custom_pydantic_types.PydanticProbabilityFloat]
pydantic-field
required
¶The probability when the latent normal variable equals zero
rhos: List[classiq.interface.helpers.custom_pydantic_types.PydanticNonOneProbabilityFloat]
pydantic-field
required
¶The sensitivity of the probability to changes in the latent normal variable values
truncation_value: float
pydantic-field
required
¶The truncation value of the latent normal distribution
weighted_adder
¶
WeightedAdder (FunctionParams)
pydantic-model
¶
Creates a circuit implementing a scalar product between an n-qubit state |q1,q2,...,qn> and an n-length non-negative integer vector (w1,w2,...wn), such that the result of the output register is |q1w1+q2w2+...qn*wn>. If no weights are provided, they are default to 1 for every qubit.
Source code in classiq/interface/generator/credit_risk_example/weighted_adder.py
class WeightedAdder(function_params.FunctionParams):
"""
Creates a circuit implementing a scalar product between an n-qubit state |q1,q2,...,qn> and an n-length non-negative
integer vector (w1,w2,...wn), such that the result of the output register is |q1*w1+q2*w2+...qn*wn>.
If no weights are provided, they are default to 1 for every qubit.
"""
num_state_qubits: pydantic.PositiveInt = pydantic.Field(
description="The number of input qubits"
)
weights: List[PydanticStrictNonNegativeInteger] = pydantic.Field(
default=None,
description="List of non-negative integers corresponding to the weight of each qubit",
)
@pydantic.validator("weights", always=True, pre=True)
def validate_weights(
cls,
weights: Optional[List[PydanticStrictNonNegativeInteger]],
values: Dict[str, Any],
) -> List[PydanticStrictNonNegativeInteger]:
num_state_qubits = values.get("num_state_qubits")
if num_state_qubits is None:
raise ClassiqValueError(
"Missing num_state_qubits and weights, either must be provided"
)
if weights is None:
return [PydanticStrictNonNegativeInteger(1)] * num_state_qubits
if len(weights) != num_state_qubits:
raise ClassiqValueError(LENGTH_ERROR_MESSAGE)
return weights
def num_sum_qubits(self) -> int:
sum_weights = np.sum(self.weights)
if sum_weights > 0:
return 1 + int(np.floor(np.log2(sum_weights)))
return 1
def _create_ios(self) -> None:
self._inputs = {
STATE: RegisterUserInput(name=STATE, size=self.num_state_qubits)
}
zero_input_name = get_zero_input_name(SUM)
self._create_zero_input_registers({zero_input_name: self.num_sum_qubits()})
self._outputs = {
**self._inputs,
SUM: RegisterUserInput(name=SUM, size=self.num_sum_qubits()),
}
entangler_params
¶
Entangler (FunctionParams)
pydantic-model
¶
A Father class for all entangler classes
Source code in classiq/interface/generator/entangler_params.py
class Entangler(FunctionParams):
"""
A Father class for all entangler classes
"""
qubit_count: pydantic.PositiveInt = pydantic.Field(
description="The number of qubits for the entangler."
)
schmidt_rank: pydantic.NonNegativeInt = pydantic.Field(
default=0, description="The required schmidt rank (log of schmidt number)."
)
def _create_ios(self) -> None:
self._inputs = {IN_NAME: RegisterUserInput(name=IN_NAME, size=self.qubit_count)}
self._outputs = {
OUT_NAME: RegisterUserInput(name=OUT_NAME, size=self.qubit_count)
}
GridEntangler (Entangler)
pydantic-model
¶
creates a graph state in the form of multi-dimensional grid according to the specified number of qubits and Schmidt rank. If possible the grid will include the exact Schmidt rank if not a smaller grid with a lower schmidt rank is constructed - as close as possible to the specified parameters. if the specified Schmidt rank is too high a 'long' grid with the maximal possible Schmidt rank width is constructed (that still obeys the condition that the largest dimension minus 1 is larger then the sum of the (d_i - 1) -- d_i including all other dimensions)
Source code in classiq/interface/generator/entangler_params.py
class GridEntangler(Entangler):
"""
creates a graph state in the form of multi-dimensional grid according to the specified number of qubits and Schmidt
rank. If possible the grid will include the exact Schmidt rank if not a smaller grid with a lower schmidt rank is
constructed - as close as possible to the specified parameters. if the specified Schmidt rank is too high a 'long'
grid with the maximal possible Schmidt rank width is constructed (that still obeys the condition that the largest
dimension minus 1 is larger then the sum of the (d_i - 1) -- d_i including all other dimensions)
"""
grid_randomization: bool = pydantic.Field(
default=True,
description="Boolean determining whether the grid structure is randomly selected out of all grids which provide"
"the same Schmidt rank width. If False the grid with maximal number of dimensions is selected.",
)
filling_factor: PydanticProbabilityFloat = pydantic.Field(
default=1,
description="float determining the fraction of cz gates that are included in a circuit for a given grid "
"structure. For example, for filling_factor=0.5 half of the cz gates required for the full grid structure are "
"included in the output circuit. The cz gates included in the circuit are chosen randomaly.",
)
filling_factor: PydanticProbabilityFloat
pydantic-field
¶
float determining the fraction of cz gates that are included in a circuit for a given grid structure. For example, for filling_factor=0.5 half of the cz gates required for the full grid structure are included in the output circuit. The cz gates included in the circuit are chosen randomaly.
grid_randomization: bool
pydantic-field
¶
Boolean determining whether the grid structure is randomly selected out of all grids which providethe same Schmidt rank width. If False the grid with maximal number of dimensions is selected.
HypercubeEntangler (Entangler)
pydantic-model
¶
Creates a cluster/graph state in the form of a hypercube with the specified number of qubits. The hypercube is constructed by building cubes of growing dimension therefore if the number of qubits is not a a power of 2 (n=2^k) the last cube will not be completed. for example if n = 11 = 2^3 + 3 a three dimensional cube is constructed connected to additional 3 qubits in the natural order (that is, these qubits will be: 1000, 1001, 1010)
Source code in classiq/interface/generator/entangler_params.py
class HypercubeEntangler(Entangler):
"""
Creates a cluster/graph state in the form of a hypercube with the specified number of qubits. The hypercube is
constructed by building cubes of growing dimension therefore if the number of qubits is not a a power of 2 (n=2^k)
the last cube will not be completed. for example if n = 11 = 2^3 + 3 a three dimensional cube is constructed
connected to additional 3 qubits in the natural order
(that is, these qubits will be: 1000, 1001, 1010)
"""
pass
TwoDimensionalEntangler (Entangler)
pydantic-model
¶
Creates a two dimensional cluster state with the specified number of qubits and schmidt rank (log of schmidt number). When the desired schmidt rank is too high, a rectangular grid with schmidt rank floor(sqrt(qubit_count))-1 is generated.
Source code in classiq/interface/generator/entangler_params.py
class TwoDimensionalEntangler(Entangler):
"""
Creates a two dimensional cluster state with the specified number of qubits and schmidt rank
(log of schmidt number). When the desired schmidt rank is too high, a rectangular grid with schmidt rank
floor(sqrt(qubit_count))-1 is generated.
"""
pass
expressions
special
¶
evaluated_expression
¶
EvaluatedExpression
dataclass
¶
EvaluatedExpression(value: Union[int, float, list, bool, classiq.interface.generator.expressions.qmod_struct_instance.QmodStructInstance, classiq.interface.generator.expressions.qmod_sized_proxy.QmodSizedProxy, classiq.interface.generator.expressions.type_proxy.TypeProxy, classiq.interface.generator.expressions.handle_identifier.HandleIdentifier, sympy.core.expr.Expr, sympy.logic.boolalg.Boolean])
Source code in classiq/interface/generator/expressions/evaluated_expression.py
@dataclass(frozen=True)
class EvaluatedExpression:
value: ExpressionValue
def is_constant(self, constant_type: Optional[Type] = None) -> bool:
if self.value is None:
return False
return isinstance(
self.value,
get_args(RuntimeConstant) if constant_type is None else constant_type,
)
def as_constant_type(self, constant_type: Type) -> Any:
if not self.is_constant():
raise ClassiqValueError(
f"Invalid access to expression {self.value!r} as {constant_type}"
)
return constant_type(self.value)
def to_int_value(self) -> int:
return self.as_constant_type(int)
def to_bool_value(self) -> bool:
return self.as_constant_type(bool)
def to_float_value(self) -> float:
return self.as_constant_type(float)
def to_list(self) -> list:
return self.as_constant_type(list)
def to_handle(self) -> HandleIdentifier:
if not isinstance(self.value, HandleIdentifier):
raise ClassiqValueError(
f"Invalid access to expression {self.value} as HandleIdentifier"
)
return self.value
def to_struct_dict(self) -> Mapping[str, Any]:
if not isinstance(self.value, QmodStructInstance):
raise ClassiqValueError(
f"Invalid access to expression {self.value} as SympyStructInstance"
)
return self.value.fields
def as_expression(self) -> str:
if self.value is None:
raise ClassiqValueError("Invalid access to unevaluated expression")
return str(self.value)
def is_identifier(self) -> bool:
return (
isinstance(self.value, Expr)
and re.fullmatch(EXECUTION_PARAMETER_PATTERN, str(self.value)) is not None
)
expression
¶
Expression (HashableASTNode)
pydantic-model
¶
Source code in classiq/interface/generator/expressions/expression.py
class Expression(HashableASTNode):
expr: str
_evaluated_expr: Optional[EvaluatedExpression] = PrivateAttr(default=None)
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
self._try_to_immediate_evaluate()
@pydantic.validator("expr")
def validate_expression(cls, expr: str) -> str:
supported_functions = (
SUPPORTED_ATOMIC_EXPRESSION_FUNCTIONS
| set(SYMPY_SUPPORTED_EXPRESSIONS)
| set(DEFAULT_SUPPORTED_FUNC_NAMES)
)
validate_expression_str(expr, supported_functions=supported_functions)
return expr
def is_evaluated(self) -> bool:
return self._evaluated_expr is not None
def as_constant(self, constant_type: Type) -> Any:
return self.value.as_constant_type(constant_type)
def to_int_value(self) -> int:
return self.as_constant(int)
def to_bool_value(self) -> bool:
return self.as_constant(bool)
def to_float_value(self) -> float:
return self.as_constant(float)
def to_struct_dict(self) -> Mapping[str, Any]:
return self.value.to_struct_dict()
def to_list(self) -> list:
return self.as_constant(list)
def _try_to_immediate_evaluate(self) -> None:
try:
result = ast.literal_eval(self.expr)
if isinstance(result, (int, float, bool)):
self._evaluated_expr = EvaluatedExpression(value=result)
except Exception: # noqa: S110
pass
@property
def value(self) -> EvaluatedExpression:
if self._evaluated_expr is None:
raise ClassiqError(f"Trying to access unevaluated value {self.expr}")
return self._evaluated_expr
def as_expression(self) -> str:
return self.value.as_expression()
def is_constant(self) -> bool:
return self.value.is_constant()
class Config:
frozen = True
def __str__(self) -> str:
return self.expr
__str__(self)
special
¶Return str(self).
Source code in classiq/interface/generator/expressions/expression.py
def __str__(self) -> str:
return self.expr
handle_identifier
¶
HandleIdentifier
dataclass
¶
HandleIdentifier(id: int)
Source code in classiq/interface/generator/expressions/handle_identifier.py
@dataclasses.dataclass(frozen=True)
class HandleIdentifier:
id: int
qmod_qscalar_proxy
¶
QmodQScalarProxy (Symbol, QmodSizedProxy)
¶
Source code in classiq/interface/generator/expressions/qmod_qscalar_proxy.py
class QmodQScalarProxy(Symbol, QmodSizedProxy):
def __new__(cls, name: str, **assumptions: bool) -> "QmodQScalarProxy":
return super().__new__(cls, name, **assumptions)
def __init__(self, name: str, size: int) -> None:
super().__init__(size)
self.name = name
@property
def handle(self) -> HandleBinding:
return HandleBinding(name=self.name)
__new__(cls, name, **assumptions)
special
staticmethod
¶Symbols are identified by name and assumptions::
from sympy import Symbol Symbol("x") == Symbol("x") True Symbol("x", real=True) == Symbol("x", real=False) False
Source code in classiq/interface/generator/expressions/qmod_qscalar_proxy.py
def __new__(cls, name: str, **assumptions: bool) -> "QmodQScalarProxy":
return super().__new__(cls, name, **assumptions)
finance
¶
Finance (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/finance.py
class Finance(function_params.FunctionParams):
model: Union[GaussianModelInput, LogNormalModelInput] = pydantic.Field(
description="Load a financial model", discriminator="kind"
)
finance_function: FinanceFunctionInput = pydantic.Field(
description="The finance function to solve the model"
)
def _create_ios(self) -> None:
finance_model = FinanceModels(model=self.model)
# 1 for the objective qubit
function_size = sum(
reg.size for reg in finance_model._outputs.values() if reg is not None
)
self._inputs = {
FUNCTION_INPUT_NAME: RegisterUserInput(
name=FUNCTION_INPUT_NAME, size=function_size
),
OBJECTIVE_INPUT_NAME: RegisterUserInput(name=OBJECTIVE_INPUT_NAME, size=1),
}
self._outputs = {
FUNCTION_OUTPUT_NAME: RegisterUserInput(
name=FUNCTION_OUTPUT_NAME, size=function_size
),
OBJECTIVE_OUTPUT_NAME: RegisterUserInput(
name=OBJECTIVE_OUTPUT_NAME, size=1
),
}
finance_function: FinanceFunctionInput
pydantic-field
required
¶
The finance function to solve the model
model: Union[classiq.interface.finance.gaussian_model_input.GaussianModelInput, classiq.interface.finance.log_normal_model_input.LogNormalModelInput]
pydantic-field
required
¶
Load a financial model
FinanceModels (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/finance.py
class FinanceModels(function_params.FunctionParams):
model: Union[GaussianModelInput, LogNormalModelInput] = pydantic.Field(
description="Load a financial model"
)
def _create_ios(self) -> None:
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterUserInput(
name=DEFAULT_OUTPUT_NAME, size=self.model.num_output_qubits
)
}
if isinstance(self.model, GaussianModelInput):
self._inputs = {
DEFAULT_INPUT_NAME: RegisterUserInput(
name=DEFAULT_INPUT_NAME, size=self.model.num_bernoulli_qubits
)
}
self._create_zero_input_registers(
{DEFAULT_ZERO_NAME: self.model.num_output_qubits}
)
self._outputs[DEFAULT_BERNOULLI_OUTPUT_NAME] = RegisterUserInput(
name=DEFAULT_BERNOULLI_OUTPUT_NAME,
size=self.model.num_bernoulli_qubits,
)
elif isinstance(self.model, LogNormalModelInput):
self._inputs = {
DEFAULT_INPUT_NAME: RegisterUserInput(
name=DEFAULT_INPUT_NAME, size=self.model.num_model_qubits
)
}
model: Union[classiq.interface.finance.gaussian_model_input.GaussianModelInput, classiq.interface.finance.log_normal_model_input.LogNormalModelInput]
pydantic-field
required
¶
Load a financial model
FinancePayoff (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/finance.py
class FinancePayoff(function_params.FunctionParams):
finance_function: FinanceFunctionInput = pydantic.Field(
description="The finance function to solve the model"
)
num_qubits: pydantic.PositiveInt
distribution_range: Tuple[float, float]
def _create_ios(self) -> None:
self._inputs = {
DEFAULT_INPUT_NAME: RegisterUserInput(
name=DEFAULT_INPUT_NAME, size=self.num_qubits
)
}
self._create_zero_input_registers({DEFAULT_ZERO_NAME: 1})
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterUserInput(name=DEFAULT_OUTPUT_NAME, size=1),
DEFAULT_POST_INPUT_NAME: RegisterUserInput(
name=DEFAULT_INPUT_NAME, size=self.num_qubits
),
}
finance_function: FinanceFunctionInput
pydantic-field
required
¶
The finance function to solve the model
functions
special
¶
classical_function_declaration
¶
ClassicalFunctionDeclaration (FunctionDeclaration)
pydantic-model
¶
Facilitates the creation of a common classical function interface object.
Source code in classiq/interface/generator/functions/classical_function_declaration.py
class ClassicalFunctionDeclaration(FunctionDeclaration):
"""
Facilitates the creation of a common classical function interface object.
"""
return_type: Optional[ConcreteClassicalType] = pydantic.Field(
description="The type of the classical value that is returned by the function (for classical functions)",
default=None,
)
BUILTIN_FUNCTION_DECLARATIONS: ClassVar[
Dict[str, "ClassicalFunctionDeclaration"]
] = {}
FOREIGN_FUNCTION_DECLARATIONS: ClassVar[
Dict[str, "ClassicalFunctionDeclaration"]
] = {}
def update_logic_flow(
self, function_dict: Mapping[str, "ClassicalFunctionDeclaration"]
) -> None:
pass
return_type: Annotated[Union[classiq.interface.generator.functions.classical_type.Integer, classiq.interface.generator.functions.classical_type.Real, classiq.interface.generator.functions.classical_type.Bool, classiq.interface.generator.functions.classical_type.ClassicalList, classiq.interface.generator.functions.classical_type.Pauli, classiq.interface.generator.functions.classical_type.StructMetaType, classiq.interface.generator.functions.classical_type.Struct, classiq.interface.generator.functions.classical_type.ClassicalArray, classiq.interface.generator.functions.classical_type.VQEResult, classiq.interface.generator.functions.classical_type.Histogram, classiq.interface.generator.functions.classical_type.Estimation, classiq.interface.generator.functions.classical_type.LadderOperator, classiq.interface.generator.functions.classical_type.IQAERes], FieldInfo(default=PydanticUndefined, discriminator='kind', extra={})]
pydantic-field
¶The type of the classical value that is returned by the function (for classical functions)
classical_type
¶
Struct (ClassicalType)
pydantic-model
¶
Source code in classiq/interface/generator/functions/classical_type.py
class Struct(ClassicalType):
kind: Literal["struct_instance"]
name: str = pydantic.Field(description="The struct type of the instance")
@property
def default_value(self) -> Any:
return super().default_value
@pydantic.root_validator(pre=True)
def _set_kind(cls, values: Dict[str, Any]) -> Dict[str, Any]:
return values_with_discriminator(values, "kind", "struct_instance")
@property
def qmod_type(self) -> type:
return type(self.name, (CStructBase,), dict())
name: str
pydantic-field
required
¶The struct type of the instance
foreign_function_definition
¶
SynthesisForeignFunctionDefinition (SynthesisQuantumFunctionDeclaration)
pydantic-model
¶
Facilitates the creation of a user-defined elementary function
This class sets extra to forbid so that it can be used in a Union and not "steal" objects from other classes.
Source code in classiq/interface/generator/functions/foreign_function_definition.py
class SynthesisForeignFunctionDefinition(SynthesisQuantumFunctionDeclaration):
"""
Facilitates the creation of a user-defined elementary function
This class sets extra to forbid so that it can be used in a Union and not "steal"
objects from other classes.
"""
register_mapping: RegisterMappingData = pydantic.Field(
default_factory=RegisterMappingData,
description="The PortDirection data that is common to all implementations of the function",
)
implementations: Optional[ImplementationsType] = pydantic.Field(
description="The implementations of the custom function",
)
@pydantic.validator("register_mapping")
def _validate_register_mapping(
cls, register_mapping: RegisterMappingData
) -> RegisterMappingData:
if not register_mapping.output_registers:
raise ClassiqValueError(
"The outputs of a custom function must be non-empty"
)
return register_mapping
@pydantic.validator("implementations", pre=True)
def _parse_implementations(
cls,
implementations: Optional[Union[ImplementationsType, FunctionImplementation]],
) -> Optional[ImplementationsType]:
if isinstance(implementations, FunctionImplementation):
return (implementations,)
return implementations
@pydantic.validator("implementations")
def _validate_implementations(
cls,
implementations: Optional[ImplementationsType],
values: Dict[str, Any],
) -> Optional[ImplementationsType]:
if not implementations:
raise ClassiqValueError(
"The implementations of a custom function must be non-empty."
)
register_mapping = values.get("register_mapping")
assert isinstance(register_mapping, RegisterMappingData)
for impl in implementations:
impl.validate_ranges_of_all_registers(register_mapping=register_mapping)
return implementations
@property
def inputs(self) -> ArithmeticIODict:
return _map_reg_user_input(self.register_mapping.input_registers)
@property
def outputs(self) -> ArithmeticIODict:
return _map_reg_user_input(self.register_mapping.output_registers)
def renamed(self, new_name: str) -> SynthesisForeignFunctionDefinition:
return SynthesisForeignFunctionDefinition(
name=new_name,
implementations=self.implementations,
register_mapping=self.register_mapping,
)
@property
def port_declarations(self) -> Dict[IOName, SynthesisPortDeclaration]:
raise ClassiqValueError(
"Bad usage of foreign function definition: port_declarations"
)
@port_declarations.setter
def port_declarations(self, value: Any) -> NoReturn:
raise ClassiqValueError(
"Bad usage of foreign function definition: port_declarations"
)
implementations: Tuple[classiq.interface.generator.functions.function_implementation.FunctionImplementation, ...]
pydantic-field
¶The implementations of the custom function
register_mapping: RegisterMappingData
pydantic-field
¶The PortDirection data that is common to all implementations of the function
function_declaration
¶
FunctionDeclaration (ASTNode, ABC)
pydantic-model
¶
Facilitates the creation of a common function interface object.
Source code in classiq/interface/generator/functions/function_declaration.py
class FunctionDeclaration(ASTNode, abc.ABC):
"""
Facilitates the creation of a common function interface object.
"""
name: str = pydantic.Field(description="The name of the function")
param_decls: Dict[str, ConcreteClassicalType] = pydantic.Field(
description="The expected interface of the functions parameters",
default_factory=dict,
)
class Config:
extra = pydantic.Extra.forbid
name: str
pydantic-field
required
¶The name of the function
param_decls: Dict[str, Annotated[Union[classiq.interface.generator.functions.classical_type.Integer, classiq.interface.generator.functions.classical_type.Real, classiq.interface.generator.functions.classical_type.Bool, classiq.interface.generator.functions.classical_type.ClassicalList, classiq.interface.generator.functions.classical_type.Pauli, classiq.interface.generator.functions.classical_type.StructMetaType, classiq.interface.generator.functions.classical_type.Struct, classiq.interface.generator.functions.classical_type.ClassicalArray, classiq.interface.generator.functions.classical_type.VQEResult, classiq.interface.generator.functions.classical_type.Histogram, classiq.interface.generator.functions.classical_type.Estimation, classiq.interface.generator.functions.classical_type.LadderOperator, classiq.interface.generator.functions.classical_type.IQAERes], FieldInfo(default=PydanticUndefined, discriminator='kind', extra={})]]
pydantic-field
¶The expected interface of the functions parameters
function_implementation
¶
FunctionImplementation (BaseModel)
pydantic-model
¶
Facilitates the creation of a user-defined custom function implementation
Source code in classiq/interface/generator/functions/function_implementation.py
class FunctionImplementation(BaseModel):
"""
Facilitates the creation of a user-defined custom function implementation
"""
class Config:
validate_all = True
extra = "forbid"
name: Optional[PydanticNonEmptyString] = pydantic.Field(
default=None,
description="The name of the custom implementation",
)
serialized_circuit: PydanticNonEmptyString = pydantic.Field(
description="The QASM code of the custom function implementation",
)
auxiliary_registers: RegistersStrictType = pydantic.Field(
default_factory=tuple,
description="A tuple of auxiliary registers to the custom implementation",
)
def num_qubits_in_all_registers(self, register_mapping: RegisterMappingData) -> int:
all_input_registers = (
register_mapping.input_registers
+ register_mapping.zero_input_registers
+ list(self.auxiliary_registers)
)
input_qubits = set(
chain.from_iterable(register.qubits for register in all_input_registers)
)
return len(input_qubits)
@pydantic.validator(
"auxiliary_registers",
pre=True,
always=True,
)
def validate_all_registers_are_tuples(
cls,
registers: RegistersType,
) -> RegistersStrictType:
if isinstance(registers, Register):
return (registers,)
return registers
def validate_ranges_of_all_registers(
self, register_mapping: RegisterMappingData
) -> None:
input_registers = register_mapping.input_registers
output_registers = register_mapping.output_registers
zero_input_registers = register_mapping.zero_input_registers
auxiliary_registers = list(self.auxiliary_registers)
all_input_registers = (
input_registers + zero_input_registers + auxiliary_registers
)
input_qubits = set(
chain.from_iterable(register.qubits for register in all_input_registers)
)
num_qubits = len(input_qubits)
all_qubits = set(range(num_qubits))
if num_qubits != sum(register.width for register in all_input_registers):
raise ClassiqValueError("The input registers must not overlap.")
if input_qubits != all_qubits:
raise ClassiqValueError(
"The set of qubits contained in all registers must be consecutive."
)
all_output_registers = output_registers + auxiliary_registers
output_qubits = set(
chain.from_iterable(register.qubits for register in all_output_registers)
)
if len(output_qubits) != sum(
register.width for register in all_output_registers
):
raise ClassiqValueError("The output registers must not overlap.")
if not output_qubits == all_qubits:
raise ClassiqValueError(
"The input and output qubits must be mutually consistent."
)
auxiliary_registers: Tuple[classiq.interface.generator.functions.register.Register, ...]
pydantic-field
¶A tuple of auxiliary registers to the custom implementation
name: ConstrainedStrValue
pydantic-field
¶The name of the custom implementation
serialized_circuit: ConstrainedStrValue
pydantic-field
required
¶The QASM code of the custom function implementation
native_function_definition
¶
IOData (BaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/functions/native_function_definition.py
class IOData(pydantic.BaseModel):
wire: WireName = pydantic.Field(
description="The name of the wire of the PortDirection data."
)
reg: RegisterUserInput = pydantic.Field(
description="The register information about the PortDirection data."
)
class Config:
frozen = True
SynthesisNativeFunctionDefinition (SynthesisQuantumFunctionDeclaration)
pydantic-model
¶
Facilitates the creation of a user-defined composite function
This class sets extra to forbid so that it can be used in a Union and not "steal" objects from other classes.
Source code in classiq/interface/generator/functions/native_function_definition.py
class SynthesisNativeFunctionDefinition(SynthesisQuantumFunctionDeclaration):
"""
Facilitates the creation of a user-defined composite function
This class sets extra to forbid so that it can be used in a Union and not "steal"
objects from other classes.
"""
input_ports_wiring: Dict[IOName, WireName] = pydantic.Field(
description="The mapping between the functions input ports, to inner wires",
default_factory=dict,
)
output_ports_wiring: Dict[IOName, WireName] = pydantic.Field(
description="The mapping between the functions output ports, to inner wires",
default_factory=dict,
)
body: List[SynthesisQuantumFunctionCall] = pydantic.Field(
default_factory=list, description="List of function calls to perform."
)
def validate_body(self) -> None:
function_call_names = {call.name for call in self.body}
if len(function_call_names) != len(self.body):
raise ClassiqValueError(LOGIC_FLOW_DUPLICATE_NAME_ERROR_MSG)
flow_graph.validate_legal_wiring(
self.body,
flow_input_names=list(self.input_ports_wiring.values()),
flow_output_names=list(self.output_ports_wiring.values()),
)
flow_graph.validate_acyclic_logic_flow(
self.body,
flow_input_names=list(self.input_ports_wiring.values()),
flow_output_names=list(self.output_ports_wiring.values()),
)
@classmethod
def _validate_direction_ports(
cls,
port_declarations: Dict[IOName, SynthesisPortDeclaration],
directions_external_port_wiring: WireDict,
direction: PortDirection,
) -> None:
for io_name in directions_external_port_wiring:
if (
io_name not in port_declarations
or not port_declarations[io_name].direction == direction
):
raise ClassiqValueError(
f"The wired {direction} port {io_name!r} is not declared."
)
@pydantic.root_validator
def validate_ports(cls, values: Dict[str, Any]) -> Dict[str, Any]:
port_declarations: Optional[Dict[IOName, SynthesisPortDeclaration]] = (
values.get("port_declarations")
)
if port_declarations is None:
return values
cls._validate_direction_ports(
port_declarations,
values.get("input_ports_wiring", dict()),
PortDirection.Input,
)
cls._validate_direction_ports(
port_declarations,
values.get("output_ports_wiring", dict()),
PortDirection.Output,
)
return values
@pydantic.validator("input_ports_wiring", always=True)
def _populate_input_ports_wiring(
cls, input_ports_wiring: Dict[IOName, WireName], values: Dict[str, Any]
) -> Dict[IOName, WireName]:
return _validate_ports_wiring_for_direction(
input_ports_wiring, values, PortDirection.Input
)
@pydantic.validator("output_ports_wiring", always=True)
def _populate_output_ports_wiring(
cls, output_ports_wiring: Dict[IOName, WireName], values: Dict[str, Any]
) -> Dict[IOName, WireName]:
return _validate_ports_wiring_for_direction(
output_ports_wiring, values, PortDirection.Output
)
body: List[classiq.interface.generator.quantum_function_call.SynthesisQuantumFunctionCall]
pydantic-field
¶List of function calls to perform.
input_ports_wiring: Dict[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue]
pydantic-field
¶The mapping between the functions input ports, to inner wires
output_ports_wiring: Dict[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue]
pydantic-field
¶The mapping between the functions output ports, to inner wires
quantum_function_declaration
¶
SynthesisQuantumFunctionDeclaration (BaseModel)
pydantic-model
¶
Facilitates the creation of a common quantum function interface object.
Source code in classiq/interface/generator/functions/quantum_function_declaration.py
class SynthesisQuantumFunctionDeclaration(BaseModel):
"""
Facilitates the creation of a common quantum function interface object.
"""
name: str = pydantic.Field(description="The name of the function")
port_declarations: Dict[IOName, SynthesisPortDeclaration] = pydantic.Field(
description="The input and output ports of the function.",
default_factory=dict,
)
@property
def input_set(self) -> Set[IOName]:
return set(self.inputs.keys())
@property
def output_set(self) -> Set[IOName]:
return set(self.outputs.keys())
@property
def inputs(self) -> ArithmeticIODict:
return _ports_to_registers(self.port_declarations, PortDirection.Input)
@property
def outputs(self) -> ArithmeticIODict:
return _ports_to_registers(self.port_declarations, PortDirection.Output)
@pydantic.validator("port_declarations")
def _validate_port_declarations_names(
cls, port_declarations: Dict[IOName, SynthesisPortDeclaration]
) -> Dict[IOName, SynthesisPortDeclaration]:
validate_nameables_mapping(port_declarations, "Port")
return port_declarations
class Config:
extra = pydantic.Extra.forbid
register
¶
Register (BaseModel)
pydantic-model
¶
A user-defined custom register.
Source code in classiq/interface/generator/functions/register.py
class Register(BaseModel):
"""
A user-defined custom register.
"""
name: PydanticNonEmptyString = pydantic.Field(
description="The name of the custom register",
)
qubits: QubitsType = pydantic.Field(
description="A tuple of qubits as integers as indexed within a custom function code",
)
@property
def width(self) -> pydantic.PositiveInt:
"""The number of qubits of the custom register"""
return len(self.qubits)
@pydantic.validator("qubits")
def validate_qubits(cls, qubits: QubitsType) -> QubitsType:
if len(qubits) == 0:
raise ClassiqValueError("qubits field must be non-empty.")
if len(set(qubits)) != len(qubits):
raise ClassiqValueError("All qubits of a register must be distinct.")
return qubits
name: ConstrainedStrValue
pydantic-field
required
¶The name of the custom register
qubits: Tuple[pydantic.types.NonNegativeInt, ...]
pydantic-field
required
¶A tuple of qubits as integers as indexed within a custom function code
width: pydantic.PositiveInt
property
readonly
¶The number of qubits of the custom register
grover_diffuser
¶
GroverDiffuser (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/grover_diffuser.py
class GroverDiffuser(FunctionParams):
variables: List[RegisterUserInput]
state_preparation: str = pydantic.Field(
default="", description="State preparation function"
)
state_preparation_params: GroverStatePreparation = pydantic.Field(
description="State preparation function parameters",
default_factory=CustomFunction,
)
def _create_ios(self) -> None:
self._inputs = {reg.name: reg for reg in self.variables}
self._outputs = {reg.name: reg for reg in self.variables}
@pydantic.root_validator(pre=True)
def _validate_state_preparation_name(cls, values: Dict[str, Any]) -> Dict[str, Any]:
if isinstance(
values.get("state_preparation_params"), CustomFunction
) and not values.get("state_preparation"):
raise ClassiqValueError(
"Must receive the function name from the `state_preparation` field for user defined functions"
)
return values
@pydantic.root_validator(pre=True)
def _parse_state_preparation(cls, values: Dict[str, Any]) -> Dict[str, Any]:
parse_function_params_values(
values=values,
params_key="state_preparation_params",
discriminator_key="state_preparation",
param_classes={StatePreparation, CustomFunction},
default_parser_class=CustomFunction,
)
return values
@pydantic.validator("variables")
def _validate_variables(
cls, variables: List[RegisterUserInput]
) -> List[RegisterUserInput]:
names = {reg.name for reg in variables}
assert len(variables) == len(names), "Repeating names not allowed"
return variables
@pydantic.validator("state_preparation_params")
def _validate_state_preparation(
cls, state_preparation_params: GroverStatePreparation, values: Dict[str, Any]
) -> GroverStatePreparation:
variables = values.get("variables", list())
sp_inputs = state_preparation_params.inputs_full(strict_zero_ios=False)
sp_outputs = state_preparation_params.outputs
if len(sp_inputs) == 1 and len(sp_outputs) == 1:
var_size = sum(reg.size for reg in variables)
assert (
state_preparation_params.num_input_qubits(strict_zero_ios=False)
== var_size
)
assert state_preparation_params.num_output_qubits == var_size
else:
variable_names_and_sizes = cls._names_and_sizes(
{var.name: var for var in variables}
)
assert cls._names_and_sizes(sp_inputs) == variable_names_and_sizes
assert cls._names_and_sizes(sp_outputs) == variable_names_and_sizes
return state_preparation_params
@staticmethod
def _names_and_sizes(transputs: ArithmeticIODict) -> Set[Tuple[str, int]]:
return {(name, reg.size) for name, reg in transputs.items()}
state_preparation: str
pydantic-field
¶
State preparation function
state_preparation_params: Union[classiq.interface.generator.state_preparation.state_preparation.StatePreparation, classiq.interface.generator.user_defined_function_params.CustomFunction]
pydantic-field
¶
State preparation function parameters
grover_operator
¶
GroverOperator (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/grover_operator.py
class GroverOperator(FunctionParams):
oracle: str = pydantic.Field(
default=_DEFAULT_ORACLE_DISCRIMINATOR, description="Oracle function"
)
oracle_params: OracleABC = pydantic.Field(description="Oracle function parameters")
state_preparation: str = pydantic.Field(
default="", description="State preparation function"
)
state_preparation_params: GroverStatePreparation = pydantic.Field(
default=None, description="State preparation function parameters"
)
def _create_ios(self) -> None:
self._inputs = {**self.oracle_params.inputs}
self._outputs = {**self.oracle_params.outputs}
@pydantic.root_validator(pre=True)
def _parse_oracle(cls, values: Dict[str, Any]) -> Dict[str, Any]:
oracle_params = values.get("oracle_params")
if isinstance(oracle_params, dict):
values["oracle_params"] = parse_function_params(
params=oracle_params,
discriminator=values.get("oracle", _DEFAULT_ORACLE_DISCRIMINATOR),
param_classes=oracle_function_param_library.param_list,
no_discriminator_error=ClassiqValueError("Invalid oracle name"),
bad_function_error=ClassiqValueError("Invalid oracle params"),
)
elif isinstance(oracle_params, FunctionParams):
values["oracle"] = oracle_params.discriminator()
else:
raise ClassiqValueError("Invalid oracle params")
return values
@pydantic.validator("state_preparation_params", always=True)
def _validate_state_preparation(
cls,
state_preparation_params: Optional[GroverStatePreparation],
values: Dict[str, Any],
) -> GroverStatePreparation:
oracle = values.get("oracle_params")
assert oracle is not None, "Must receive an oracle"
state_preparation_params = (
state_preparation_params
or cls._default_state_preparation_params(
num_qubits=oracle.num_input_qubits(strict_zero_ios=True)
)
)
assert GroverDiffuser(
state_preparation_params=state_preparation_params,
state_preparation=values.get("state_preparation", ""),
variables=oracle.variables(),
), "Cannot construct a GroverDiffuser"
return state_preparation_params
@staticmethod
def _default_state_preparation_params(num_qubits: int) -> StatePreparation:
num_states: int = 2**num_qubits
return StatePreparation(
probabilities=[1.0 / float(num_states)] * num_states,
error_metric={
Metrics.L2: NonNegativeFloatRange(lower_bound=0.0, upper_bound=0.0)
},
)
def get_diffuser(self) -> GroverDiffuser:
return GroverDiffuser(
variables=self.oracle_params.variables(),
state_preparation=self.state_preparation,
state_preparation_params=self.state_preparation_params,
)
oracle: str
pydantic-field
¶
Oracle function
oracle_params: OracleABC
pydantic-field
required
¶
Oracle function parameters
state_preparation: str
pydantic-field
¶
State preparation function
state_preparation_params: Union[classiq.interface.generator.state_preparation.state_preparation.StatePreparation, classiq.interface.generator.user_defined_function_params.CustomFunction]
pydantic-field
¶
State preparation function parameters
hamiltonian_evolution
special
¶
exponentiation
¶
Exponentiation (HamiltonianEvolution)
pydantic-model
¶
Exponentiation of a Hermitian Pauli sum operator.
Source code in classiq/interface/generator/hamiltonian_evolution/exponentiation.py
class Exponentiation(HamiltonianEvolution):
"""
Exponentiation of a Hermitian Pauli sum operator.
"""
evolution_coefficient: float = pydantic.Field(
default=1.0, description="A global coefficient multiplying the operator."
)
constraints: ExponentiationConstraints = pydantic.Field(
default_factory=ExponentiationConstraints,
description="Constraints for the exponentiation.",
)
optimization: ExponentiationOptimization = pydantic.Field(
default=ExponentiationOptimization.MINIMIZE_DEPTH,
description="What attribute to optimize.",
)
@pydantic.validator("pauli_operator")
def _validate_is_hermitian(cls, pauli_operator: PauliOperator) -> PauliOperator:
return operator.validate_operator_is_hermitian(pauli_operator)
ExponentiationConstraints (BaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/hamiltonian_evolution/exponentiation.py
class ExponentiationConstraints(pydantic.BaseModel):
max_depth: Optional[pydantic.PositiveInt] = pydantic.Field(
default=None, description="Maximum depth of the exponentiation circuit."
)
max_error: Optional[pydantic.PositiveFloat] = pydantic.Field(
default=None,
description="Maximum approximation error of the exponentiation circuit.",
)
class Config:
frozen = True
hamiltonian_evolution
¶
HamiltonianEvolution (FunctionParams, ABC)
pydantic-model
¶
Suzuki trotterization of a Hermitian operator
Source code in classiq/interface/generator/hamiltonian_evolution/hamiltonian_evolution.py
class HamiltonianEvolution(FunctionParams, ABC):
"""
Suzuki trotterization of a Hermitian operator
"""
pauli_operator: PauliOperator = pydantic.Field(
description="A weighted sum of Pauli strings."
)
use_naive_evolution: bool = pydantic.Field(
default=False, description="Whether to evolve the operator naively."
)
def _create_ios(self) -> None:
self._inputs = {
DEFAULT_INPUT_NAME: RegisterArithmeticInfo(
size=self.pauli_operator.num_qubits
)
}
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterArithmeticInfo(
size=self.pauli_operator.num_qubits
)
}
qdrift
¶
QDrift (HamiltonianEvolution)
pydantic-model
¶
qDrift trotterization of a Hermitian operator; see https://arxiv.org/abs/1811.08017
Source code in classiq/interface/generator/hamiltonian_evolution/qdrift.py
class QDrift(HamiltonianEvolution):
"""
qDrift trotterization of a Hermitian operator; see https://arxiv.org/abs/1811.08017
"""
evolution_coefficient: ParameterFloatType = pydantic.Field(
default=1.0,
description="A global coefficient multiplying the operator.",
is_exec_param=True,
)
num_qdrift: pydantic.PositiveInt = pydantic.Field(
description="The number of elements in the qDrift product.",
)
@pydantic.validator("pauli_operator")
def _validate_is_hermitian(cls, pauli_operator: PauliOperator) -> PauliOperator:
return operator.validate_operator_is_hermitian(pauli_operator)
suzuki_trotter
¶
SuzukiParameters (BaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/hamiltonian_evolution/suzuki_trotter.py
class SuzukiParameters(pydantic.BaseModel):
order: pydantic.PositiveInt = pydantic.Field(
default=1,
description="The order of the Suzuki-Trotter. Supports only order equals to 1 or an even number",
)
repetitions: pydantic.NonNegativeInt = pydantic.Field(
default=1, description="The number of repetitions in the Suzuki-Trotter"
)
@pydantic.validator("order")
def _validate_order(cls, order: int) -> int:
if order != 1 and order % 2:
raise ClassiqValueError(
f"Odd order greater than 1 is not supported. Got {order}"
)
return order
class Config:
frozen = True
SuzukiTrotter (HamiltonianEvolution)
pydantic-model
¶
Suzuki trotterization of a Hermitian operator
Source code in classiq/interface/generator/hamiltonian_evolution/suzuki_trotter.py
class SuzukiTrotter(HamiltonianEvolution):
"""
Suzuki trotterization of a Hermitian operator
"""
evolution_coefficient: ParameterFloatType = pydantic.Field(
default=1.0,
description="A global coefficient multiplying the operator.",
is_exec_param=True,
)
suzuki_parameters: SuzukiParameters = pydantic.Field(
default_factory=SuzukiParameters, description="The Suziki parameters."
)
disable_scheduling: bool = pydantic.Field(
default=False, description="Whether to disable the reordering of Pauli terms."
)
@pydantic.validator("pauli_operator")
def _validate_no_complex_coefficients(
cls, pauli_operator: PauliOperator
) -> PauliOperator:
return operator.validate_operator_has_no_complex_coefficients(pauli_operator)
hardware
special
¶
hardware_data
¶
HardwareData (BaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/hardware/hardware_data.py
class HardwareData(pydantic.BaseModel):
basis_gates: List[str] = pydantic.Field(
default=list(),
description="The basis gates of the hardware. "
"This set will be used during the model optimization. "
"If none given, use default values: "
f"If no connectivity map is given or the connectivity map is symmetric - {sorted(DEFAULT_BASIS_GATES)}. "
f"If a non-symmetric connectivity map is given - {sorted(DEFAULT_ROUTING_BASIS_GATES)}. ",
)
connectivity_map: Optional[ConnectivityMap] = pydantic.Field(
default=None,
description="Qubit connectivity map, in the form [ [q0, q1], [q1, q2],...]. "
"If none given, assume the hardware is fully connected",
)
is_symmetric_connectivity: bool = pydantic.Field(
default=True,
description="Assumes that the coupling map forms an undirected graph, "
"so for every qubit pair [q0, q1], both qubits can act as control and target. "
"If false, the first / second qubit denotes the control / target, respectively",
)
@pydantic.validator("connectivity_map")
def _validate_connectivity_map(
cls, connectivity_map: Optional[ConnectivityMap]
) -> Optional[ConnectivityMap]:
if connectivity_map is None:
return connectivity_map
if not connectivity_map:
raise ClassiqValueError("Connectivity map cannot be empty")
connectivity_map = _reindex_qubits(connectivity_map)
return connectivity_map
@pydantic.root_validator()
def _symmetrize_connectivity_map(cls, values: Dict[str, Any]) -> Dict[str, Any]:
connectivity_map = values.get("connectivity_map")
if connectivity_map is None:
return values
is_symmetric = values.get("is_symmetric_connectivity")
if is_symmetric:
connectivity_map = _symmetrize_connectivity_map(connectivity_map)
values["connectivity_map"] = connectivity_map
if not _is_connected_map(connectivity_map):
raise ClassiqValueError(
f"Connectivity map must be connected: {connectivity_map} is not connected."
)
return values
@pydantic.root_validator()
def _validate_basis_gates(cls, values: Dict[str, Any]) -> Dict[str, Any]:
connectivity_map = values.get("connectivity_map")
specified_basis_gates = values.get("basis_gates", [])
if connectivity_map is None:
values["basis_gates"] = specified_basis_gates or list(DEFAULT_BASIS_GATES)
return values
is_symmetric_connectivity = values.get("is_symmetric")
if is_symmetric_connectivity or _check_symmetry(connectivity_map):
values["basis_gates"] = specified_basis_gates or list(DEFAULT_BASIS_GATES)
return values
values["basis_gates"] = specified_basis_gates or list(
DEFAULT_ROUTING_BASIS_GATES
)
invalid_gates = [
gate
for gate in specified_basis_gates
if gate in TWO_QUBIT_GATES and gate not in ROUTING_TWO_QUBIT_BASIS_GATES
]
if invalid_gates:
raise ClassiqValueError(
"Connectivity-aware synthesis with non-symmetric coupling map "
"is currently supported for the following two-qubit gates only: cx, ecr, rzx."
)
return values
basis_gates: List[str]
pydantic-field
¶The basis gates of the hardware. This set will be used during the model optimization. If none given, use default values: If no connectivity map is given or the connectivity map is symmetric - ['cx', 'cy', 'cz', 'h', 'id', 'p', 'r', 'rx', 'ry', 'rz', 's', 'sdg', 'sx', 'sxdg', 't', 'tdg', 'u', 'u1', 'u2', 'x', 'y', 'z']. If a non-symmetric connectivity map is given - ['cx', 'h', 'id', 'p', 'r', 'rx', 'ry', 'rz', 's', 'sdg', 'sx', 'sxdg', 't', 'tdg', 'u', 'u1', 'u2', 'x', 'y', 'z'].
connectivity_map: List[types.ConstrainedListValue]
pydantic-field
¶Qubit connectivity map, in the form [ [q0, q1], [q1, q2],...]. If none given, assume the hardware is fully connected
is_symmetric_connectivity: bool
pydantic-field
¶Assumes that the coupling map forms an undirected graph, so for every qubit pair [q0, q1], both qubits can act as control and target. If false, the first / second qubit denotes the control / target, respectively
hardware_efficient_ansatz
¶
HardwareEfficientAnsatz (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/hardware_efficient_ansatz.py
class HardwareEfficientAnsatz(function_params.FunctionParams):
connectivity_map: ConnectivityMapType = pydantic.Field(
default=None,
description="Hardware's connectivity map, in the form [ [x0, x1], [x1, x2],...]. "
"If none specified - use connectivity map from the model hardware settings. "
"If none specified as well, all qubit pairs will be connected.",
)
num_qubits: pydantic.PositiveInt = pydantic.Field(
default=None,
description="Number of qubits in the ansatz.",
)
reps: pydantic.PositiveInt = pydantic.Field(
default=1, description="Number of layers in the Ansatz"
)
one_qubit_gates: Union[str, List[str]] = pydantic.Field(
default=["x", "ry"],
description='List of gates for the one qubit gates layer, e.g. ["x", "ry"]',
)
two_qubit_gates: Union[str, List[str]] = pydantic.Field(
default=["cx"],
description='List of gates for the two qubit gates entangling layer, e.g. ["cx", "cry"]',
)
parameter_prefix: str = pydantic.Field(
default="param_",
description="Prefix for the generated parameters",
)
@pydantic.validator("num_qubits", pre=True, always=True)
def validate_num_qubits(
cls, num_qubits: Optional[pydantic.PositiveInt], values: Dict[str, Any]
) -> pydantic.PositiveInt:
connectivity_map = values.get("connectivity_map")
conn_map_is_not_list = (
isinstance(connectivity_map, SupportedConnectivityMaps)
or connectivity_map is None
)
if num_qubits is None and conn_map_is_not_list:
raise ClassiqValueError(_NUM_QUBITS_NOT_PROVIDED_ERROR)
if num_qubits is None:
if conn_map_is_not_list:
raise ValueError(_NUM_QUBITS_NOT_PROVIDED_ERROR)
if TYPE_CHECKING:
assert connectivity_map is not None
return len(set(itertools.chain.from_iterable(connectivity_map)))
if conn_map_is_not_list:
return num_qubits
if TYPE_CHECKING:
assert connectivity_map is not None
invalid_qubits = {
qubit
for qubit in itertools.chain.from_iterable(connectivity_map)
if qubit >= num_qubits
}
if invalid_qubits:
raise ClassiqValueError(
f"Invalid qubits: {invalid_qubits} "
f"out of range specified by num_qubits: [0, {num_qubits - 1}]"
)
return num_qubits
@pydantic.validator("one_qubit_gates")
def validate_one_qubit_gates(
cls, one_qubit_gates: Union[str, List[str]]
) -> Union[str, List[str]]:
one_qubit_gates_list = (
[one_qubit_gates] if isinstance(one_qubit_gates, str) else one_qubit_gates
)
for one_qubit_gate in one_qubit_gates_list:
if one_qubit_gate not in SINGLE_QUBIT_GATES:
raise ClassiqValueError(f"Invalid one qubit gate: {one_qubit_gate}")
return one_qubit_gates
@pydantic.validator("two_qubit_gates")
def validate_two_qubit_gates(
cls, two_qubit_gates: Union[str, List[str]]
) -> Union[str, List[str]]:
two_qubit_gates_list = (
[two_qubit_gates] if isinstance(two_qubit_gates, str) else two_qubit_gates
)
for two_qubit_gate in two_qubit_gates_list:
if two_qubit_gate not in TWO_QUBIT_GATES:
raise ClassiqValueError(f"Invalid two qubit gate: {two_qubit_gate}")
return two_qubit_gates
def _create_ios(self) -> None:
self._inputs = {
DEFAULT_INPUT_NAME: RegisterUserInput(
name=DEFAULT_INPUT_NAME, size=self.num_qubits
)
}
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterUserInput(
name=DEFAULT_OUTPUT_NAME, size=self.num_qubits
)
}
connectivity_map: Union[List[types.ConstrainedListValue], classiq.interface.generator.hardware_efficient_ansatz.SupportedConnectivityMaps]
pydantic-field
¶
Hardware's connectivity map, in the form [ [x0, x1], [x1, x2],...]. If none specified - use connectivity map from the model hardware settings. If none specified as well, all qubit pairs will be connected.
num_qubits: PositiveInt
pydantic-field
¶
Number of qubits in the ansatz.
one_qubit_gates: Union[str, List[str]]
pydantic-field
¶
List of gates for the one qubit gates layer, e.g. ["x", "ry"]
parameter_prefix: str
pydantic-field
¶
Prefix for the generated parameters
reps: PositiveInt
pydantic-field
¶
Number of layers in the Ansatz
two_qubit_gates: Union[str, List[str]]
pydantic-field
¶
List of gates for the two qubit gates entangling layer, e.g. ["cx", "cry"]
hva
¶
HVA (ChemistryFunctionParams)
pydantic-model
¶
Hamiltonian Variational Ansatz
Source code in classiq/interface/generator/hva.py
class HVA(ChemistryFunctionParams):
"""
Hamiltonian Variational Ansatz
"""
reps: pydantic.PositiveInt = pydantic.Field(
default=1, description="Number of layers in the Ansatz"
)
use_naive_evolution: bool = pydantic.Field(
default=False, description="Whether to evolve the operator naively"
)
parameter_prefix: str = pydantic.Field(
default="param_",
description="Prefix for the generated parameters",
)
identity
¶
Identity (FunctionParams)
pydantic-model
¶
Source code in classiq/interface/generator/identity.py
class Identity(FunctionParams):
arguments: NonEmptyRegisterUserInputList = pydantic.Field(
description="registers describing the state (ordered)"
)
@pydantic.validator("arguments")
def _validate_argument_names(
cls, arguments: List[RegisterUserInput]
) -> List[RegisterUserInput]:
return [
arg if arg.name else arg.revalued(name=cls._get_default_arg_name(index))
for index, arg in enumerate(arguments)
]
def _create_ios(self) -> None:
self._inputs = {arg.name: arg for arg in self.arguments}
self._outputs = {arg.name: arg for arg in self.arguments}
@staticmethod
def _get_default_arg_name(index: int) -> str:
return f"arg_{index}"
def get_power_order(self) -> int:
return 1
arguments: ConstrainedListValue
pydantic-field
required
¶
registers describing the state (ordered)
inequality_mixer
¶
InequalityMixer (FunctionParams)
pydantic-model
¶
Mixing a fixed point number variable below a given upper bound or above a given lower bound. i.e. after applying this function the variable will hold a superposition position of all the valid values.
Source code in classiq/interface/generator/inequality_mixer.py
class InequalityMixer(function_params.FunctionParams):
"""
Mixing a fixed point number variable below a given upper bound or above a given
lower bound. i.e. after applying this function the variable will hold a
superposition position of all the valid values.
"""
data_reg_input: RegisterArithmeticInfo = pydantic.Field(
description="The input variable to mix."
)
bound_reg_input: RegisterOrConst = pydantic.Field(
description="Fixed number or variable that define the upper or lower bound for"
" the mixing operation. In case of a fixed number bound, the value"
" must be positive."
)
mixer_parameter: ParameterFloatType = pydantic.Field(
description="The parameter used for rotation gates in the mixer.",
is_exec_param=True,
)
is_less_inequality: bool = pydantic.Field(
default=True,
description="Whether to mix below or above a certain bound."
"Less inequality mixes between 0 and the given bound."
"Greater inequality mixes between the bound and the maximal number allowed by"
" the number of qubits (i.e 2^n - 1).",
)
def _create_ios(self) -> None:
self._inputs = {DATA_REG_INPUT_NAME: self.data_reg_input}
self._outputs = {DATA_REG_OUTPUT_NAME: self.data_reg_input}
if isinstance(self.bound_reg_input, RegisterArithmeticInfo):
self._inputs[BOUND_REG_INPUT_NAME] = self.bound_reg_input
self._outputs[BOUND_REG_OUTPUT_NAME] = self.bound_reg_input
bound_reg_input: Union[classiq.interface.generator.arith.register_user_input.RegisterArithmeticInfo, float]
pydantic-field
required
¶
Fixed number or variable that define the upper or lower bound for the mixing operation. In case of a fixed number bound, the value must be positive.
data_reg_input: RegisterArithmeticInfo
pydantic-field
required
¶
The input variable to mix.
is_less_inequality: bool
pydantic-field
¶
Whether to mix below or above a certain bound.Less inequality mixes between 0 and the given bound.Greater inequality mixes between the bound and the maximal number allowed by the number of qubits (i.e 2^n - 1).
mixer_parameter: Union[float, str]
pydantic-field
required
¶
The parameter used for rotation gates in the mixer.
linear_pauli_rotations
¶
LinearPauliRotations (FunctionParams)
pydantic-model
¶
Perform independent linear rotations on target qubits, each controlled by an identical n-qubit state register |x>.
Each target qubit, indexed with k and denoted by q_k, undergoes the following transformation: |x>|q_k> -> |x> * [cos(theta(x,k)/2) + isin(theta(x,k)/2)sigma]|q_k> with sigma being 'X', 'Y' or 'Z' Pauli matrix, and the angle is a linear function of the state, theta(x,k)/2 = (slope(k)*x + offset(k))/2.
For example, a 'Y' rotation on one target qubit will result in a circuit implementing the following logic: |x>|0> -> cos((slopex + offset)/2)|x>|0> + sin((slopex + offset)/2)|x>|1>
!!! q_0 "─────────────────────────■───────── ... ──────────────────────"
│
.
│
q_(n-1): ─────────────────────────┼───────── ... ───────────■──────────
┌────────────┐ ┌───────┴───────┐ ┌─────────┴─────────┐
!!! target "─┤ RY(offset) ├──┤ RY(2^0 slope) ├ ... ┤ RY(2^(n-1) slope) ├"
└────────────┘ └───────────────┘ └───────────────────┘
Source code in classiq/interface/generator/linear_pauli_rotations.py
class LinearPauliRotations(function_params.FunctionParams):
"""
Perform independent linear rotations on target qubits, each controlled by an identical
n-qubit state register |x>.
Each target qubit, indexed with k and denoted by q_k, undergoes the following transformation:
|x>|q_k> -> |x> * [cos(theta(x,k)/2) + i*sin(theta(x,k)/2)*sigma]|q_k>
with sigma being 'X', 'Y' or 'Z' Pauli matrix, and the angle is a linear function of the state,
theta(x,k)/2 = (slope(k)*x + offset(k))/2.
For example, a 'Y' rotation on one target qubit will result in a circuit implementing the following logic:
|x>|0> -> cos((slope*x + offset)/2)|x>|0> + sin((slope*x + offset)/2)|x>|1>
q_0: ─────────────────────────■───────── ... ──────────────────────
│
.
│
q_(n-1): ─────────────────────────┼───────── ... ───────────■──────────
┌────────────┐ ┌───────┴───────┐ ┌─────────┴─────────┐
target: ─┤ RY(offset) ├──┤ RY(2^0 slope) ├ ... ┤ RY(2^(n-1) slope) ├
└────────────┘ └───────────────┘ └───────────────────┘
"""
num_state_qubits: pydantic.PositiveInt = pydantic.Field(
description="The number of input qubits"
)
bases: List[PydanticPauliBasisStr] = pydantic.Field(
description="The types of Pauli rotations ('X', 'Y', 'Z')."
)
slopes: List[float] = pydantic.Field(
description="The slopes of the controlled rotations."
)
offsets: List[float] = pydantic.Field(
description="The offsets of the controlled rotations."
)
@pydantic.validator("bases", "slopes", "offsets", pre=True, always=True)
def as_list(cls, v: Any) -> List[Any]:
if not isinstance(v, list):
v = [v]
return v
@pydantic.root_validator()
def validate_lists(cls, values: Dict[str, Any]) -> Dict[str, Any]:
offsets = values.get("offsets", list())
bases = values.get("bases", list())
slopes = values.get("slopes", list())
if len(slopes) == len(offsets) and len(offsets) == len(bases):
return values
raise ClassiqValueError(LENGTH_ERROR_MESSAGE)
def _create_ios(self) -> None:
self._inputs = {
STATE: RegisterArithmeticInfo(size=self.num_state_qubits),
TARGET: RegisterArithmeticInfo(size=len(self.bases)),
}
self._outputs = {**self.inputs}
bases: List[classiq.interface.generator.linear_pauli_rotations.ConstrainedStrValue]
pydantic-field
required
¶
The types of Pauli rotations ('X', 'Y', 'Z').
num_state_qubits: PositiveInt
pydantic-field
required
¶
The number of input qubits
offsets: List[float]
pydantic-field
required
¶
The offsets of the controlled rotations.
slopes: List[float]
pydantic-field
required
¶
The slopes of the controlled rotations.
mcu
¶
Mcu (FunctionParams)
pydantic-model
¶
Multi-controlled u-gate. Based on U(theta, phi, lam, gam) = e^(i*(gam + (phi + lam)/2)) * RZ(phi) * RY(theta) * RZ(lam) For a general gate U, four angles are required - theta, phi, lambda and gam.
U(gam, phi,theta, lam) = e^(igam) * cos(theta/2) & -e^(ilam)sin(theta/2) \ e^(iphi)sin(theta/2) & e^(i(phi+lam))*cos(theta/2) \
U(gam, phi,theta, lam) = e^(igam) * cos(theta/2) & -e^(ilam)sin(theta/2) \ e^(iphi)sin(theta/2) & e^(i(phi+lam))*cos(theta/2) \
Source code in classiq/interface/generator/mcu.py
class Mcu(FunctionParams):
"""
Multi-controlled u-gate.
Based on U(theta, phi, lam, gam) = e^(i*(gam + (phi + lam)/2)) * RZ(phi) * RY(theta) * RZ(lam)
For a general gate U, four angles are required - theta, phi, lambda and gam.
U(gam, phi,theta, lam) =
e^(i*gam) *
cos(theta/2) & -e^(i*lam)*sin(theta/2) \\
e^(i*phi)*sin(theta/2) & e^(i*(phi+lam))*cos(theta/2) \\
U(gam, phi,theta, lam) =
e^(i*gam) *
cos(theta/2) & -e^(i*lam)*sin(theta/2) \\
e^(i*phi)*sin(theta/2) & e^(i*(phi+lam))*cos(theta/2) \\
"""
theta: ParameterFloatType = pydantic.Field(
default=0, description="Theta radian angle.", is_exec_param=True
)
phi: ParameterFloatType = pydantic.Field(
default=0, description="Phi radian angle.", is_exec_param=True
)
lam: ParameterFloatType = pydantic.Field(
default=0, description="Lambda radian angle.", is_exec_param=True
)
gam: ParameterFloatType = pydantic.Field(
default=0, description="gam radian angle.", is_exec_param=True
)
num_ctrl_qubits: Optional[pydantic.PositiveInt] = pydantic.Field(
default=None, description="The number of control qubits."
)
ctrl_state: Optional[str] = pydantic.Field(
default=None, description="string of the control state"
)
@pydantic.root_validator()
def _validate_control(cls, values: Dict[str, Any]) -> Dict[str, Any]:
num_ctrl_qubits = values.get("num_ctrl_qubits")
ctrl_state = values.get("ctrl_state")
if ctrl_state is not None:
ctrl_state = cast(str, ctrl_state)
ControlState.validate_control_string(ctrl_state)
if ctrl_state is None and num_ctrl_qubits is None:
raise ClassiqValueError("num_ctrl_qubits or ctrl_state must exist.")
if ctrl_state is None and num_ctrl_qubits is not None:
values["ctrl_state"] = "1" * num_ctrl_qubits
ctrl_state = values["ctrl_state"]
if num_ctrl_qubits is None and ctrl_state is not None:
num_ctrl_qubits = len(ctrl_state)
values["num_ctrl_qubits"] = num_ctrl_qubits
if len(ctrl_state) != num_ctrl_qubits:
raise ClassiqValueError(
"control state length should be equal to the number of control qubits"
)
return values
def _create_ios(self) -> None:
if self.num_ctrl_qubits is None:
raise ClassiqValueError("num_ctrl_qubits must have a valid value.")
ctrl_register = RegisterUserInput(size=self.num_ctrl_qubits, name=CTRL)
target_register = RegisterUserInput(size=1, name=TARGET)
self._inputs = {reg.name: reg for reg in (ctrl_register, target_register)}
self._outputs = {reg.name: reg for reg in (ctrl_register, target_register)}
ctrl_state: str
pydantic-field
¶
string of the control state
gam: Union[float, str]
pydantic-field
¶
gam radian angle.
lam: Union[float, str]
pydantic-field
¶
Lambda radian angle.
num_ctrl_qubits: PositiveInt
pydantic-field
¶
The number of control qubits.
phi: Union[float, str]
pydantic-field
¶
Phi radian angle.
theta: Union[float, str]
pydantic-field
¶
Theta radian angle.
mcx
¶
Mcx (FunctionParams)
pydantic-model
¶
multi-controlled x-gate
Source code in classiq/interface/generator/mcx.py
class Mcx(FunctionParams):
"""
multi-controlled x-gate
"""
arguments: List[RegisterUserInput] = pydantic.Field(
default_factory=list, description="registers describing the state (ordered)"
)
num_ctrl_qubits: Optional[pydantic.PositiveInt] = pydantic.Field(
description="number of control qubits"
)
ctrl_state: str = pydantic.Field(default="", description="control state string")
@pydantic.validator("arguments", always=True)
def _validate_argument_names(
cls, arguments: List[RegisterUserInput]
) -> List[RegisterUserInput]:
register_name_list: List[Optional[str]] = [arg.name for arg in arguments]
if None in register_name_list:
raise ClassiqValueError("All registers must be named")
if len(set(register_name_list)) != len(register_name_list):
raise ClassiqValueError("Registers must have distinct names")
return arguments
@pydantic.validator("num_ctrl_qubits", always=True)
def _validate_sizes(cls, num_ctrl_qubits: int, values: Dict[str, Any]) -> int:
arguments_size = sum(arg.size for arg in values.get("arguments", list()))
if not num_ctrl_qubits:
num_ctrl_qubits = arguments_size - 1
if num_ctrl_qubits < 1:
raise ClassiqValueError("Must have control qubits")
if arguments_size == 0:
ctrl_register = RegisterUserInput(size=num_ctrl_qubits, name=CTRL)
target_register = RegisterUserInput(size=1, name=TARGET_QUBIT)
values["arguments"] = [ctrl_register, target_register]
elif num_ctrl_qubits != arguments_size - 1:
raise ClassiqValueError("Given sizes do not match")
return num_ctrl_qubits
@pydantic.validator("ctrl_state", always=True)
def _validate_ctrl_state(cls, ctrl_state: str, values: Dict[str, Any]) -> str:
num_ctrl_qubits = values.get("num_ctrl_qubits", -1)
if not ctrl_state:
return "1" * num_ctrl_qubits
if len(ctrl_state) != num_ctrl_qubits:
raise ClassiqValueError(
"control state length should be equal to the number of control qubits"
)
ControlState.validate_control_string(ctrl_state)
return ctrl_state
def _create_ios(self) -> None:
self._inputs = {arg.name: arg for arg in self.arguments}
self._outputs = {arg.name: arg for arg in self.arguments}
def get_power_order(self) -> int:
return 2
model
special
¶
constraints
¶
Constraints (BaseModel)
pydantic-model
¶
Input constraints for the generated quantum circuit.
Source code in classiq/interface/generator/model/constraints.py
class Constraints(BaseModel, extra=Extra.forbid):
"""
Input constraints for the generated quantum circuit.
"""
max_width: Optional[pydantic.PositiveInt] = pydantic.Field(
default=None,
description="Maximum number of qubits in generated quantum circuit",
)
max_depth: Optional[pydantic.PositiveInt] = None
max_gate_count: Dict[TranspilerBasisGates, pydantic.NonNegativeInt] = (
pydantic.Field(default_factory=lambda: defaultdict(int))
)
optimization_parameter: OptimizationParameterType = pydantic.Field(
default=OptimizationParameter.NO_OPTIMIZATION,
description="If set, the synthesis engine optimizes the solution"
" according to that chosen parameter",
)
max_width: PositiveInt
pydantic-field
¶Maximum number of qubits in generated quantum circuit
optimization_parameter: Union[classiq.interface.generator.model.constraints.OptimizationParameter, classiq.interface.generator.transpiler_basis_gates.TranspilerBasisGates]
pydantic-field
¶If set, the synthesis engine optimizes the solution according to that chosen parameter
model
¶
ClassiqBaseModel (VersionedModel, ABC)
pydantic-model
¶
All the relevant data for evaluating execution in one place.
Source code in classiq/interface/generator/model/model.py
class ClassiqBaseModel(VersionedModel, ABC):
"""
All the relevant data for evaluating execution in one place.
"""
types: List[StructDeclaration] = pydantic.Field(
default_factory=list,
description="The user-defined custom function library.",
)
constants: List[Constant] = pydantic.Field(
default_factory=list,
)
classical_execution_code: str = pydantic.Field(
description="The classical execution code of the model", default=""
)
execution_preferences: ExecutionPreferences = pydantic.Field(
default_factory=ExecutionPreferences
)
@pydantic.validator("types")
def types_validator(cls, types: List[StructDeclaration]) -> List[StructDeclaration]:
if not is_list_unique([struct_type.name for struct_type in types]):
raise ClassiqValueError(TYPE_LIBRARY_DUPLICATED_TYPE_NAMES)
return types
classical_execution_code: str
pydantic-field
¶The classical execution code of the model
types: List[classiq.interface.generator.types.struct_declaration.StructDeclaration]
pydantic-field
¶The user-defined custom function library.
__json_encoder__(obj)
special
staticmethod
¶partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
ExecutionModel (ClassiqBaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/model/model.py
class ExecutionModel(ClassiqBaseModel):
circuit_outputs: ArithmeticIODict = pydantic.Field(
description="Mapping between a measured register name and its arithmetic type",
default_factory=dict,
)
circuit_outputs: Dict[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, classiq.interface.generator.arith.register_user_input.RegisterArithmeticInfo]
pydantic-field
¶Mapping between a measured register name and its arithmetic type
__json_encoder__(obj)
special
staticmethod
¶partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
SynthesisModel (ClassiqBaseModel)
pydantic-model
¶
All the relevant data for generating quantum circuit in one place.
Source code in classiq/interface/generator/model/model.py
class SynthesisModel(ClassiqBaseModel):
"""
All the relevant data for generating quantum circuit in one place.
"""
kind: Literal["synthesis"] = pydantic.Field(default="synthesis")
# Must be validated before logic_flow
functions: List[ConcreteFunctionDefinition] = pydantic.Field(
default_factory=_create_default_functions,
description="The quantum functions of the model.",
)
constraints: Constraints = pydantic.Field(default_factory=Constraints)
preferences: Preferences = pydantic.Field(default_factory=Preferences)
def __init__(
self,
*,
body: Optional[List[SynthesisQuantumFunctionCall]] = None,
inputs: Optional[WireDict] = None,
outputs: Optional[WireDict] = None,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
if body:
self.main_func.body.extend(body)
if inputs:
self.set_inputs(
{
name: QRegGenericAlias(
QReg(DEFAULT_PORT_SIZE), (DEFAULT_PORT_SIZE, 0)
)
for name in inputs.keys()
},
inputs,
)
if outputs:
self.set_outputs(
{name: QReg(DEFAULT_PORT_SIZE) for name in outputs.keys()}, outputs
)
@property
def main_func(self) -> SynthesisNativeFunctionDefinition:
return self.function_dict[MAIN_FUNCTION_NAME] # type:ignore[return-value]
@property
def body(self) -> List[SynthesisQuantumFunctionCall]:
return self.main_func.body
@property
def inputs(self) -> WireDict:
return self.main_func.input_ports_wiring
def set_inputs(
self,
inputs: Mapping[IOName, QRegGenericAlias],
input_wiring: Mapping[IOName, WireName],
) -> None:
self._update_main_declarations(inputs, PortDeclarationDirection.Input)
self.main_func.input_ports_wiring.update(input_wiring)
@property
def outputs(self) -> WireDict:
return self.main_func.output_ports_wiring
def set_outputs(
self, outputs: Mapping[IOName, QReg], output_wiring: Mapping[IOName, WireName]
) -> None:
self._update_main_declarations(outputs, PortDeclarationDirection.Output)
self.main_func.output_ports_wiring.update(output_wiring)
@pydantic.validator("preferences", always=True)
def _seed_suffix_randomizer(cls, preferences: Preferences) -> Preferences:
SUFFIX_RANDOMIZER.seed(preferences.random_seed)
return preferences
def _get_qualified_direction(
self, port_name: str, direction: PortDeclarationDirection
) -> PortDeclarationDirection:
if port_name in self.main_func.port_declarations:
return PortDeclarationDirection.Inout
return direction
def _update_main_declarations(
self,
value: Union[Mapping[IOName, QReg], Mapping[IOName, QRegGenericAlias]],
direction: PortDeclarationDirection,
) -> None:
for port_name, register in value.items():
if isinstance(register, QReg):
size = len(register)
is_signed = getattr(register, "is_signed", False) or False
fraction_places = getattr(register, "fraction_places", 0) or 0
else:
size = register.size if register.size is not None else DEFAULT_PORT_SIZE
is_signed = False
fraction_places = (
register.fraction_places
if register.fraction_places is not None
else 0
)
self.main_func.port_declarations[port_name] = SynthesisPortDeclaration(
name=port_name,
size=size,
direction=self._get_qualified_direction(port_name, direction),
is_signed=is_signed,
fraction_places=fraction_places,
)
@property
def function_dict(self) -> Dict[str, SynthesisQuantumFunctionDeclaration]:
return nameables_to_dict(self.functions)
@pydantic.validator("functions", each_item=True)
def validate_static_correctness(
cls, func_def: ConcreteFunctionDefinition
) -> ConcreteFunctionDefinition:
if isinstance(func_def, SynthesisNativeFunctionDefinition):
func_def.validate_body()
return func_def
@pydantic.validator("functions")
def validate_main_function_exists(
cls, func_defs: List[ConcreteFunctionDefinition]
) -> List[ConcreteFunctionDefinition]:
if MAIN_FUNCTION_NAME not in {func.name for func in func_defs}:
raise ClassiqValueError("The model must contain a `main` function")
return func_defs
def get_model(self) -> SerializedModel:
return SerializedModel(self.json(exclude_defaults=True, indent=2))
def classical_model(self) -> ExecutionModel:
return ExecutionModel(
types=self.types,
constants=self.constants,
classical_execution_code=self.classical_execution_code,
execution_preferences=self.execution_preferences,
circuit_outputs=self.main_func.outputs,
)
functions: List[Union[classiq.interface.generator.functions.foreign_function_definition.SynthesisForeignFunctionDefinition, classiq.interface.generator.functions.native_function_definition.SynthesisNativeFunctionDefinition]]
pydantic-field
¶The quantum functions of the model.
__json_encoder__(obj)
special
staticmethod
¶partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
preferences
special
¶
preferences
¶
Preferences (BaseModel)
pydantic-model
¶Source code in classiq/interface/generator/model/preferences/preferences.py
class Preferences(pydantic.BaseModel, extra=pydantic.Extra.forbid):
_backend_preferences: Optional[BackendPreferences] = pydantic.PrivateAttr(
default=None
)
machine_precision: PydanticMachinePrecision = DEFAULT_MACHINE_PRECISION
backend_service_provider: Optional[Union[Provider, ProviderVendor, str]] = (
pydantic.Field(
default=None,
description="Provider company or cloud for the requested backend.",
)
)
backend_name: Optional[Union[PydanticBackendName, AllBackendsNameByVendor]] = (
pydantic.Field(
default=None, description="Name of the requested backend or target."
)
)
custom_hardware_settings: CustomHardwareSettings = pydantic.Field(
default_factory=CustomHardwareSettings,
description="Custom hardware settings which will be used during optimization. "
"This field is ignored if backend preferences are given.",
)
debug_mode: bool = pydantic.Field(
default=True,
description="Add debug information to the synthesized result. "
"Setting this option to False can potentially speed up the synthesis, and is "
"recommended for executing iterative algorithms.",
)
output_format: PydanticConstrainedQuantumFormatList = pydantic.Field(
default=[QuantumFormat.QASM],
description="The quantum circuit output format(s). ",
)
pretty_qasm: bool = pydantic.Field(
True,
description="Prettify the OpenQASM2 outputs (use line breaks inside the gate "
"declarations).",
)
qasm3: Optional[bool] = pydantic.Field(
None,
description="Output OpenQASM 3.0 instead of OpenQASM 2.0. Relevant only for "
"the `qasm` and `transpiled_circuit.qasm` attributes of `GeneratedCircuit`.",
)
transpilation_option: TranspilationOption = pydantic.Field(
default=TranspilationOption.AUTO_OPTIMIZE,
description="If true, the returned result will contain a "
"transpiled circuit and its depth",
)
timeout_seconds: pydantic.PositiveInt = pydantic.Field(
default=300, description="Generation timeout in seconds"
)
optimization_timeout_seconds: Optional[pydantic.PositiveInt] = pydantic.Field(
default=None,
description="Optimization timeout in seconds, or None for no "
"optimization timeout (will still timeout when the generation timeout is over)",
)
random_seed: int = pydantic.Field(
default_factory=create_random_seed,
description="The random seed used for the generation",
)
@pydantic.validator("backend_service_provider", pre=True)
def validate_backend_service_provider(
cls, backend_service_provider: Any
) -> Optional[Provider]:
if backend_service_provider is None:
return None
return validate_backend_service_provider(backend_service_provider)
@pydantic.validator("optimization_timeout_seconds")
def optimization_timeout_less_than_generation_timeout(
cls,
optimization_timeout_seconds: Optional[pydantic.PositiveInt],
values: Dict[str, Any],
) -> Optional[pydantic.PositiveInt]:
generation_timeout_seconds = values.get("timeout_seconds")
if generation_timeout_seconds is None or optimization_timeout_seconds is None:
return optimization_timeout_seconds
if optimization_timeout_seconds >= generation_timeout_seconds:
raise ClassiqValueError(
f"Generation timeout ({generation_timeout_seconds})"
f"is greater than or equal to "
f"optimization timeout ({optimization_timeout_seconds}) "
)
return optimization_timeout_seconds
@pydantic.validator("output_format", pre=True)
def make_output_format_list(cls, output_format: Any) -> List:
if not pydantic.utils.sequence_like(output_format):
output_format = [output_format]
return output_format
@pydantic.validator("output_format", always=True)
def validate_output_format(
cls, output_format: PydanticConstrainedQuantumFormatList, values: Dict[str, Any]
) -> PydanticConstrainedQuantumFormatList:
if len(output_format) != len(set(output_format)):
raise ClassiqValueError(
f"output_format={output_format}\n"
"has at least one format that appears twice or more"
)
service_provider = values.get("backend_service_provider")
if service_provider is None:
return output_format
provider_format = _SERVICE_PROVIDER_TO_FORMAT.get(service_provider)
if provider_format is not None and provider_format not in output_format:
output_format.append(provider_format)
return output_format
@pydantic.root_validator()
def validate_backend(cls, values: Dict[str, Any]) -> Dict[str, Any]:
backend_name = values.get("backend_name")
backend_service_provider = values.get("backend_service_provider")
if (backend_name is None) != (backend_service_provider is None):
raise ClassiqValueError(BACKEND_VALIDATION_ERROR_MESSAGE)
return values
@property
def backend_preferences(self) -> Optional[BackendPreferences]:
if self.backend_name is None or self.backend_service_provider is None:
return None
if self._backend_preferences is None:
self._backend_preferences = BackendPreferences(
backend_name=self.backend_name,
backend_service_provider=self.backend_service_provider,
)
return self._backend_preferences
backend_name: Union[classiq.interface.generator.model.preferences.preferences.ConstrainedStrValue, classiq.interface.backend.quantum_backend_providers.IBMQHardwareNames, classiq.interface.backend.quantum_backend_providers.AzureQuantumBackendNames, classiq.interface.backend.quantum_backend_providers.AmazonBraketBackendNames, classiq.interface.backend.quantum_backend_providers.IonqBackendNames, classiq.interface.backend.quantum_backend_providers.ClassiqNvidiaBackendNames, classiq.interface.backend.quantum_backend_providers.AliceBobBackendNames, classiq.interface.backend.quantum_backend_providers.OQCBackendNames]
pydantic-field
¶Name of the requested backend or target.
backend_service_provider: Union[classiq.interface.hardware.Provider, classiq.interface.backend.quantum_backend_providers.ProviderVendor, str]
pydantic-field
¶Provider company or cloud for the requested backend.
custom_hardware_settings: CustomHardwareSettings
pydantic-field
¶Custom hardware settings which will be used during optimization. This field is ignored if backend preferences are given.
debug_mode: bool
pydantic-field
¶Add debug information to the synthesized result. Setting this option to False can potentially speed up the synthesis, and is recommended for executing iterative algorithms.
optimization_timeout_seconds: PositiveInt
pydantic-field
¶Optimization timeout in seconds, or None for no optimization timeout (will still timeout when the generation timeout is over)
output_format: ConstrainedListValue
pydantic-field
¶The quantum circuit output format(s).
pretty_qasm: bool
pydantic-field
¶Prettify the OpenQASM2 outputs (use line breaks inside the gate declarations).
qasm3: bool
pydantic-field
¶Output OpenQASM 3.0 instead of OpenQASM 2.0. Relevant only for the qasm
and transpiled_circuit.qasm
attributes of GeneratedCircuit
.
random_seed: int
pydantic-field
¶The random seed used for the generation
timeout_seconds: PositiveInt
pydantic-field
¶Generation timeout in seconds
transpilation_option: TranspilationOption
pydantic-field
¶If true, the returned result will contain a transpiled circuit and its depth
noise_properties
¶
NoiseProperties (BaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/noise_properties.py
class NoiseProperties(BaseModel):
measurement_bit_flip_probability: Optional[PydanticProbabilityFloat] = (
pydantic.Field(
default=None,
description="Probability of measuring the wrong value for each qubit.",
)
)
measurement_bit_flip_probability: PydanticProbabilityFloat
pydantic-field
¶
Probability of measuring the wrong value for each qubit.
oracles
special
¶
custom_oracle
¶
CustomOracle (OracleABC)
pydantic-model
¶
Source code in classiq/interface/generator/oracles/custom_oracle.py
class CustomOracle(OracleABC[QubitState]):
custom_oracle: str = pydantic.Field(description="Oracle function")
custom_oracle_params: CustomFunction = pydantic.Field(
description="Oracle function parameters",
default_factory=CustomFunction,
)
@pydantic.root_validator(pre=True)
def _parse_oracle(cls, values: Dict[str, Any]) -> Dict[str, Any]:
parse_function_params_values(
values=values,
params_key="custom_oracle_params",
discriminator_key="custom_oracle",
param_classes={CustomFunction},
default_parser_class=CustomFunction,
)
return values
@pydantic.validator("custom_oracle_params")
def _validate_names_match_oracle(
cls, custom_oracle_params: CustomFunction
) -> CustomFunction:
if set(custom_oracle_params.input_decls.keys()) != set(
custom_oracle_params.output_decls.keys()
):
raise ClassiqValueError("Oracle IO names must be identical")
if any(
custom_oracle_params.output_decls[name].size != input_decl.size
for name, input_decl in custom_oracle_params.input_decls.items()
):
raise ClassiqValueError("Oracle IO sizes must be identical")
return custom_oracle_params
def _get_register_transputs(self) -> ArithmeticIODict:
return {**self.custom_oracle_params.input_decls}
def binary_result_to_typed_result(
self, bin_result: VariableBinResultMap
) -> VariableTypedResultMap[QubitState]:
return bin_result
def is_good_result(
self, problem_result: VariableTypedResultMap[QubitState]
) -> bool:
return True
partitioned_register
¶
PartitionedRegister
dataclass
¶
PartitionedRegister(name: str, num_qubits: int, partitions: Tuple[Tuple[int, ...], ...])
Source code in classiq/interface/generator/partitioned_register.py
@dataclass(frozen=True)
class PartitionedRegister:
name: str
# There are up to num_qubits qubits within the partitions, with unique values from 0 to num_qubits-1
num_qubits: int
partitions: Tuple[Tuple[int, ...], ...]
def __post_init__(self) -> None:
if not self.partitions:
message = f"Error creating {self.name}. Must contain at least one partition"
raise ClassiqValueError(message)
if not all(self.partitions):
message = f"Error creating {self.name}. Each partition must have at least one qubit"
raise ClassiqValueError(message)
partition_sets = [frozenset(part) for part in self.partitions]
intersection = frozenset.intersection(*partition_sets)
if len(self.partitions) > 1 and intersection:
message = (
f"Overlapping partitions in {self.name}. Intersection: {intersection}"
)
raise ClassiqValueError(message)
union = frozenset.union(*partition_sets)
possible_qubits = frozenset(range(self.num_qubits))
if not union <= possible_qubits:
message = f"Extra qubits in {self.name}: {union - possible_qubits}"
raise ClassiqValueError(message)
def get_partition(self, index: int) -> "RegisterPartition":
return RegisterPartition(self, index)
# Special partition containing qubits from [0..num_qubits) not in any other
# partition. May contain no qubits.
@property
def leftover_partition(self) -> "RegisterPartition":
return RegisterPartition(self, _index=None)
@property
def _leftover_qubits(self) -> Tuple[int, ...]:
total_qubits = set(itertools.chain.from_iterable(self.partitions))
return tuple(
qubit for qubit in range(self.num_qubits) if qubit not in total_qubits
)
@property
def all_qubits_in_partitions(self) -> Iterator[int]:
return itertools.chain.from_iterable(self.partitions)
def all_register_partitions(
self, include_leftover_partition: bool = False
) -> List["RegisterPartition"]:
all_partitions = [self.get_partition(i) for i in range(len(self.partitions))]
if include_leftover_partition:
all_partitions.append(self.leftover_partition)
return all_partitions
RegisterPartition
dataclass
¶
RegisterPartition(partitioned_register: classiq.interface.generator.partitioned_register.PartitionedRegister, _index: Optional[int])
Source code in classiq/interface/generator/partitioned_register.py
@dataclass(frozen=True)
class RegisterPartition:
partitioned_register: PartitionedRegister
# index == None means this is the partition containing the leftover qubits.
_index: Optional[int]
def __post_init__(self) -> None:
num_partitions = len(self.partitioned_register.partitions)
if self._index is not None and (
self._index >= num_partitions or self._index < 0
):
message = f"Partition does not exist in {self.partitioned_register.name}. Index {self._index} not in range [0, {num_partitions})"
raise ClassiqValueError(message)
@property
def qubits(self) -> Tuple[int, ...]:
if self._index is None:
return self.partitioned_register._leftover_qubits
return self.partitioned_register.partitions[self._index]
@property
def _is_single_qubit(self) -> bool:
return len(self.qubits) == 1
# io_string is, for example, 'input' or 'foo[3:5]'
def matches_string(self, io_string: str) -> bool:
name, slice_ = parse_io_slicing(io_string)
qubits = tuple(range(self.partitioned_register.num_qubits)[slice_])
return self.partitioned_register.name == name and self.qubits == qubits
qft
¶
QFT (FunctionParams)
pydantic-model
¶
Creates a quantum Fourier transform on a specified number of qubits.
Source code in classiq/interface/generator/qft.py
class QFT(FunctionParams):
"""
Creates a quantum Fourier transform on a specified number of qubits.
"""
num_qubits: pydantic.PositiveInt = pydantic.Field(
description="The number of qubits on which the QFT acts."
)
approximation_degree: pydantic.NonNegativeInt = pydantic.Field(
default=0,
description="The degree of approximation (0 for no approximation). The smallest "
"'approximation_degree' rotation angles are dropped from the QFT.",
)
do_swaps: bool = pydantic.Field(
default=True, description="Whether to include the final swaps in the QFT."
)
def _create_ios(self) -> None:
self._inputs = {
DEFAULT_INPUT_NAME: RegisterArithmeticInfo(size=self.num_qubits)
}
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterArithmeticInfo(size=self.num_qubits)
}
def get_power_order(self) -> int:
return 4
approximation_degree: NonNegativeInt
pydantic-field
¶
The degree of approximation (0 for no approximation). The smallest 'approximation_degree' rotation angles are dropped from the QFT.
do_swaps: bool
pydantic-field
¶
Whether to include the final swaps in the QFT.
num_qubits: PositiveInt
pydantic-field
required
¶
The number of qubits on which the QFT acts.
qpe
¶
ExponentiationScaling (BaseModel)
pydantic-model
¶
Details of exponentiation scaling for phase estimation.
Source code in classiq/interface/generator/qpe.py
class ExponentiationScaling(pydantic.BaseModel):
"""
Details of exponentiation scaling for phase estimation.
"""
max_depth: pydantic.PositiveInt = pydantic.Field(
description="The max_depth of the smallest exponentiation",
)
max_depth_scaling_factor: pydantic.NonNegativeFloat = pydantic.Field(
default=2.0,
description="The scaling factor of the exponentiation max_depth; defaults to 2.",
)
class Config:
frozen = True
ExponentiationSpecification (BaseModel)
pydantic-model
¶
Specifications of individual Exponentiation details for each qubit; only valid if Exponentiation is given as unitary_params for PhaseEstimation. This sets the optimization to ExponentiationOptimization.MINIMIZE_ERROR and overrides the max_depth constraints.
Source code in classiq/interface/generator/qpe.py
class ExponentiationSpecification(pydantic.BaseModel):
"""
Specifications of individual Exponentiation details for each qubit; only valid if Exponentiation is given as unitary_params for PhaseEstimation.
This sets the optimization to ExponentiationOptimization.MINIMIZE_ERROR and overrides the max_depth constraints.
"""
scaling: Optional[ExponentiationScaling] = pydantic.Field(
default=None,
description="The scaling of the exponentiation functions.",
)
max_depths: Optional[Tuple[pydantic.NonNegativeInt, ...]] = pydantic.Field(
default=None,
description="The max_depth of each exponentiation function; overrides scaling.",
)
class Config:
frozen = True
@pydantic.root_validator
def _validate_exponentiation_specification(
cls, values: Dict[str, Any]
) -> Dict[str, Any]:
if values.get("scaling") is None and values.get("max_depths") is None:
raise ClassiqValueError("At least one specification must be provided.")
return values
PhaseEstimation (FunctionParams)
pydantic-model
¶
Quantum phase estimation of a given unitary function.
Source code in classiq/interface/generator/qpe.py
class PhaseEstimation(FunctionParams):
"""
Quantum phase estimation of a given unitary function.
"""
size: pydantic.PositiveInt = pydantic.Field(
description="The number of qubits storing the estimated phase."
)
unitary: str = pydantic.Field(
description="The unitary function for phase estimation.",
)
unitary_params: FunctionParams = pydantic.Field(
description="The unitary function parameters.",
default_factory=CustomFunction,
)
exponentiation_specification: Optional[ExponentiationSpecification] = (
pydantic.Field(
default=None,
description="The specifications for phase estimation of exponentiation functions.",
)
)
_output_name: IOName = pydantic.PrivateAttr(
default=PHASE_ESTIMATION_DEFAULT_OUTPUT_NAME
)
@property
def output_name(self) -> str:
return self._output_name
def _create_ios(self) -> None:
self._inputs = {**self.unitary_params.inputs}
self._outputs = {**self.unitary_params.outputs}
self._outputs[self._output_name] = RegisterArithmeticInfo(size=self.size)
self._create_zero_input_registers({DEFAULT_ZERO_NAME: self.size})
@pydantic.root_validator(pre=True)
def _validate_composite_name(cls, values: Dict[str, Any]) -> Dict[str, Any]:
if isinstance(values.get("unitary_params"), CustomFunction) and not values.get(
"unitary"
):
raise ClassiqValueError(
"`PhaseEstimation` of a user define function (`CustomFunction`) must receive the function name from the `unitary` field"
)
return values
@pydantic.root_validator(pre=True)
def _parse_function_params(cls, values: Dict[str, Any]) -> Dict[str, Any]:
parse_function_params_values(
values=values,
params_key="unitary_params",
discriminator_key="unitary",
param_classes=function_param_library_without_self_reference.param_list,
default_parser_class=CustomFunction,
)
return values
@pydantic.validator("unitary_params")
def _validate_unitary_params(cls, unitary_params: FunctionParams) -> FunctionParams:
if not unitary_params.is_powerable():
if isinstance(unitary_params, CustomFunction):
raise ClassiqMismatchIOsError(CUSTOM_FUNCTIONS_IO_MISMATCH_ERROR)
raise ClassiqValueError(
f"Phase estimation of {unitary_params.discriminator()} is currently not supported."
)
return unitary_params
@pydantic.validator("exponentiation_specification")
def _validate_exponentiation_specification(
cls,
exponentiation_specification: Optional[ExponentiationSpecification],
values: Dict[str, Any],
) -> Optional[ExponentiationSpecification]:
if exponentiation_specification is None:
return exponentiation_specification
unitary_params = values.get("unitary_params")
if not isinstance(unitary_params, Exponentiation):
raise ClassiqValueError(
"exponentiation_specification is only valid for Exponentiation unitary_params."
)
if exponentiation_specification.max_depths is not None and len(
exponentiation_specification.max_depths
) != values.get("size"):
raise ClassiqValueError(
"Length of max_depths must match the provided size."
)
return exponentiation_specification
exponentiation_specification: ExponentiationSpecification
pydantic-field
¶
The specifications for phase estimation of exponentiation functions.
size: PositiveInt
pydantic-field
required
¶
The number of qubits storing the estimated phase.
unitary: str
pydantic-field
required
¶
The unitary function for phase estimation.
unitary_params: FunctionParams
pydantic-field
¶
The unitary function parameters.
qsvm
¶
QSVMFeatureMap (FunctionParams)
pydantic-model
¶
Feature map circuit used for QSVM
Source code in classiq/interface/generator/qsvm.py
class QSVMFeatureMap(FunctionParams):
"""
Feature map circuit used for QSVM
"""
feature_map: FeatureMapType = pydantic.Field(
description="The feature map for the qsvm",
discriminator="map_type",
)
@property
def num_qubits(self) -> int:
if not self.feature_map.feature_dimension:
raise ClassiqQSVMError(
"Feature dimension should be provided to create a circuit."
)
if isinstance(self.feature_map, QSVMFeatureMapPauli):
return self.feature_map.feature_dimension
else:
return int(np.ceil(self.feature_map.feature_dimension / 2))
def _create_ios(self) -> None:
self._inputs = {
DEFAULT_INPUT_NAME: RegisterUserInput(
name=DEFAULT_INPUT_NAME, size=self.num_qubits
)
}
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterUserInput(
name=DEFAULT_OUTPUT_NAME, size=self.num_qubits
)
}
feature_map: Union[classiq.interface.generator.qsvm.QSVMFeatureMapBlochSphere, classiq.interface.generator.qsvm.QSVMFeatureMapPauli]
pydantic-field
required
¶
The feature map for the qsvm
quantum_function_call
¶
SynthesisQuantumFunctionCall (BaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/quantum_function_call.py
class SynthesisQuantumFunctionCall(BaseModel):
function: str = pydantic.Field(description="The function that is called")
function_params: f_params.FunctionParams = pydantic.Field(
description="The parameters necessary for defining the function",
default_factory=CustomFunction,
)
is_inverse: bool = pydantic.Field(
default=False, description="Call the function inverse."
)
strict_zero_ios: bool = pydantic.Field(
default=True,
description="Enables automated qubit allocation for pre-determined zero inputs "
"and allows automated qubit release when performing inversion.\n"
"Setting this flag to False exposes zero inputs and outputs as regular "
"functional registers, and shifts the responsibility to the user to manually "
"manage qubit allocation and release.",
)
release_by_inverse: bool = pydantic.Field(
default=False, description="Release zero inputs in inverse call."
)
control_states: List[ControlState] = pydantic.Field(
default_factory=list,
description="Call the controlled function with the given controlled states.",
)
should_control: bool = pydantic.Field(
default=True,
description="False value indicates this call shouldn't be controlled even if the flow is controlled.",
)
inputs: IOType = pydantic.Field(
default_factory=dict,
description="A mapping from the input name to the wire it connects to",
)
inouts: Mapping[IOName, WirePair] = pydantic.Field(
default_factory=dict,
description="A mapping from in/out name to the wires that connect to it",
)
outputs: IOType = pydantic.Field(
default_factory=dict,
description="A mapping from the output name to the wire it connects to",
)
power: PydanticPowerType = pydantic.Field(
default=1, description="Number of successive calls to the operation"
)
name: PydanticNonEmptyString = pydantic.Field(
default=None,
description="The name of the function instance. "
"If not set, determined automatically.",
)
def __eq__(self, other: Any) -> bool:
return (
isinstance(other, SynthesisQuantumFunctionCall) and self.name == other.name
)
def __hash__(self) -> int:
return hash(self.name)
@property
def non_zero_input_wires(self) -> List[WireName]:
in_out_input_wires = [
split_wire_pair_to_wires(inout)[0] for inout in self.inouts.values()
]
return get_non_zero_wires(self.inputs_dict.values()) + in_out_input_wires
@property
def non_zero_output_wires(self) -> List[WireName]:
in_out_output_wires = [
split_wire_pair_to_wires(inout)[1] for inout in self.inouts.values()
]
return get_non_zero_wires(self.outputs_dict.values()) + in_out_output_wires
@property
def inputs_dict(self) -> WireDict:
assert isinstance(self.inputs, dict)
return self.inputs
@property
def outputs_dict(self) -> WireDict:
assert isinstance(self.outputs, dict)
return self.outputs
@property
def input_regs_dict(self) -> ArithmeticIODict:
ctrl_regs_dict = {
ctrl_state.name: ctrl_state.control_register
for ctrl_state in self.control_states
}
return {
**self._true_io_dict(io=PortDirection.Input),
**ctrl_regs_dict,
}
@property
def output_regs_dict(self) -> ArithmeticIODict:
ctrl_regs_dict = {
ctrl_state.name: ctrl_state.control_register
for ctrl_state in self.control_states
}
return {
**self._true_io_dict(io=PortDirection.Output),
**ctrl_regs_dict,
}
def _true_io_dict(self, io: PortDirection) -> ArithmeticIODict:
if (io == PortDirection.Input) != self.is_inverse:
return self.function_params.inputs_full(self.strict_zero_ios)
return self.function_params.outputs
@pydantic.validator("name", pre=True, always=True)
def _create_name(cls, name: Optional[str], values: Dict[str, Any]) -> str:
"""
generates a name to a user defined-functions as follows:
<function_name>_<SUFFIX_MARKER>_<random_suffix>
"""
if name is not None:
match = re.fullmatch(pattern=NAME_REGEX, string=name)
if match is None:
raise ClassiqValueError(BAD_CALL_NAME_ERROR_MSG)
return name
function = values.get("function")
params = values.get("function_params")
if (
isinstance(params, CustomFunction)
and function == CustomFunction.discriminator()
and params.name != ""
):
function = params.name
suffix = f"{SUFFIX_MARKER}_{randomize_suffix()}"
if not function or params is None:
return name if name else suffix
return f"{function.split(f'_{EXPANDED_KEYWORD}')[0]}_{suffix}"
@pydantic.root_validator(pre=True)
def validate_composite_name(cls, values: Dict[str, Any]) -> Dict[str, Any]:
if isinstance(values.get("unitary_params"), CustomFunction) and not values.get(
"unitary"
):
raise ClassiqValueError(
"`PhaseEstimation` of a user define function (`CustomFunction`) must receive the function name from the `unitary` field"
)
return values
@pydantic.root_validator(pre=True)
def _parse_function_params(cls, values: Dict[str, Any]) -> Dict[str, Any]:
f_params.parse_function_params_values(
values=values,
params_key="function_params",
discriminator_key="function",
param_classes=function_param_list.function_param_library.param_list,
default_parser_class=CustomFunction,
)
return values
# TODO: note that this checks QuantumFunctionCall input register names
# are PARTIAL to FunctionParams input register names, not EQUAL.
# We might want to change that.
@staticmethod
def _validate_input_names(
*,
params: f_params.FunctionParams,
inputs: WireDict,
is_inverse: bool,
control_states: List[ControlState],
strict_zero_ios: bool,
) -> None:
(
invalid_expressions,
invalid_slicings,
invalid_names,
) = SynthesisQuantumFunctionCall._get_invalid_ios(
expressions=inputs.keys(),
params=params,
io=PortDirection.Input if not is_inverse else PortDirection.Output,
control_states=control_states,
strict_zero_ios=strict_zero_ios,
)
error_msg = []
if invalid_expressions:
error_msg.append(f"{BAD_INPUT_EXPRESSION_MSG}: {invalid_expressions}")
if invalid_names:
error_msg.append(f"{BAD_INPUT_ERROR_MSG}: {invalid_names}")
if invalid_slicings:
error_msg.append(f"{BAD_INPUT_SLICING_MSG}: {invalid_slicings}")
if error_msg:
raise ClassiqValueError("\n".join(error_msg))
@pydantic.validator("strict_zero_ios")
def _validate_arithmetic_cannot_strict_zero_ios(
cls, strict_zero_ios: bool, values: Dict[str, Any]
) -> bool:
assert not (
values.get("function") == Arithmetic.discriminator() and not strict_zero_ios
), "when using the Arithmetic function, assign to the expression result register via the target parameter instead of the strict_zero_ios flag"
return strict_zero_ios
@pydantic.validator("control_states")
def _validate_control_states(
cls, control_states: List[ControlState], values: Dict[str, Any]
) -> List[ControlState]:
control_names = [ctrl_state.name for ctrl_state in control_states]
function_params = values.get("function_params")
strict_zero_ios = values.get("strict_zero_ios")
if not (
isinstance(function_params, FunctionParams)
and isinstance(strict_zero_ios, bool)
):
return control_states
all_input_names = [
*function_params.inputs_full(strict_zero_ios=strict_zero_ios),
*control_names,
]
all_output_names = [*function_params.outputs, *control_names]
if any(
cls._has_repetitions(name_list)
for name_list in (control_names, all_input_names, all_output_names)
):
raise ClassiqControlError()
return control_states
@staticmethod
def _has_repetitions(name_list: Sequence[str]) -> bool:
return len(set(name_list)) < len(name_list)
@staticmethod
def _validate_slices(
io: PortDirection,
inputs: IOType,
fp: FunctionParams,
strict_zero_ios: bool,
control_states: List[ControlState],
) -> None:
name_slice_pairs = [parse_io_slicing(input) for input in inputs]
slices_dict: Dict[str, List[slice]] = defaultdict(list)
for name, slice in name_slice_pairs:
slices_dict[name].append(slice)
fp_inputs = (
fp.inputs_full(strict_zero_ios)
if (io == PortDirection.Input)
else fp.outputs
)
widths = {name: reg.size for name, reg in fp_inputs.items()}
control_names = {state.name for state in control_states}
for name in slices_dict:
if name in control_names:
continue
assert name in widths, "Name not in widths"
if not SynthesisQuantumFunctionCall._register_validate_slices(
slices_dict[name], widths[name]
):
raise ClassiqValueError(BAD_INPUT_SLICING_MSG)
@staticmethod
def _register_validate_slices(slices: List[slice], reg_width: int) -> bool:
widths_separated = [len(range(reg_width)[reg_slice]) for reg_slice in slices]
# examples: slice(0), slice(5,None) when width <= 5, slice(5,3)
empty_slices = 0 in widths_separated
max_stop = max(reg_slice.stop or 0 for reg_slice in slices)
out_of_range = max_stop > reg_width
all_widths_separated = sum(widths_separated)
all_indices = set(
itertools.chain.from_iterable(
range(reg_width)[reg_slice] for reg_slice in slices
)
)
all_widths_combined = len(all_indices)
overlapping_slices = all_widths_combined != all_widths_separated
return not any((empty_slices, out_of_range, overlapping_slices))
@pydantic.validator("inputs")
def _validate_inputs(cls, inputs: IOType, values: Dict[str, Any]) -> WireDict:
params: Optional[FunctionParams] = values.get("function_params")
is_inverse: bool = values.get("is_inverse", False)
strict_zero_ios: bool = values.get("strict_zero_ios", True)
control_states: List[ControlState] = values.get("control_states", list())
if params is None:
return dict()
if isinstance(params, CustomFunction):
if not isinstance(inputs, dict):
raise ClassiqValueError(CUSTOM_FUNCTION_SINGLE_IO_ERROR)
return inputs
if isinstance(inputs, str):
inputs = SynthesisQuantumFunctionCall._single_wire_to_dict(
io=f_params.PortDirection.Input,
is_inverse=is_inverse,
io_wire=inputs,
params=params,
strict_zero_ios=strict_zero_ios,
)
cls._validate_input_names(
params=params,
inputs=inputs,
is_inverse=is_inverse,
control_states=control_states,
strict_zero_ios=strict_zero_ios,
)
cls._validate_slices(
PortDirection.Input if not is_inverse else PortDirection.Output,
inputs,
params,
strict_zero_ios,
control_states,
)
return inputs
@staticmethod
def _validate_output_names(
*,
params: f_params.FunctionParams,
outputs: WireDict,
is_inverse: bool,
control_states: List[ControlState],
strict_zero_ios: bool,
) -> None:
(
invalid_expressions,
invalid_slicings,
invalid_names,
) = SynthesisQuantumFunctionCall._get_invalid_ios(
expressions=outputs.keys(),
params=params,
io=PortDirection.Output if not is_inverse else PortDirection.Input,
control_states=control_states,
strict_zero_ios=strict_zero_ios,
)
error_msg = []
if invalid_expressions:
error_msg.append(f"{BAD_OUTPUT_EXPRESSION_MSG}: {invalid_expressions}")
if invalid_names:
error_msg.append(f"{BAD_OUTPUT_ERROR_MSG}: {invalid_names}")
if invalid_slicings:
error_msg.append(f"{BAD_OUTPUT_SLICING_MSG}: {invalid_slicings}")
if error_msg:
raise ClassiqValueError("\n".join(error_msg))
@pydantic.validator("outputs")
def _validate_outputs(cls, outputs: IOType, values: Dict[str, Any]) -> IOType:
params = values.get("function_params")
is_inverse: bool = values.get("is_inverse", False)
strict_zero_ios: bool = values.get("strict_zero_ios", True)
control_states = values.get("control_states", list())
if params is None:
return outputs
if isinstance(params, CustomFunction):
if not isinstance(outputs, dict):
raise ClassiqValueError(CUSTOM_FUNCTION_SINGLE_IO_ERROR)
return outputs
if isinstance(outputs, str):
outputs = SynthesisQuantumFunctionCall._single_wire_to_dict(
io=f_params.PortDirection.Output,
is_inverse=is_inverse,
io_wire=outputs,
params=params,
strict_zero_ios=strict_zero_ios,
)
cls._validate_output_names(
params=params,
outputs=outputs,
is_inverse=is_inverse,
control_states=control_states,
strict_zero_ios=strict_zero_ios,
)
cls._validate_slices(
PortDirection.Input if is_inverse else PortDirection.Output,
outputs,
params,
strict_zero_ios,
control_states,
)
return outputs
@pydantic.validator("power", always=True)
def _validate_power(
cls, power: pydantic.NonNegativeInt, values: Dict[str, Any]
) -> pydantic.NonNegativeInt:
function_params = values.get("function_params")
if function_params is None:
return power
if power != 1 and not function_params.is_powerable(
values.get("strict_zero_ios")
):
raise ClassiqValueError("Cannot power this operator")
return power
@staticmethod
def _single_wire_to_dict(
io: f_params.PortDirection,
is_inverse: bool,
io_wire: WireName,
params: f_params.FunctionParams,
strict_zero_ios: bool = True,
) -> WireDict:
params_io = list(
params.inputs_full(strict_zero_ios)
if (io == PortDirection.Input) != is_inverse
else params.outputs
)
if len(params_io) == 1:
return {list(params_io)[0]: io_wire}
error_message = _generate_single_io_err(
io_str=io.name.lower(),
io_regs=params_io,
io_wire=io_wire,
function_name=type(params).__name__,
)
raise ClassiqValueError(error_message)
@staticmethod
def _get_invalid_ios(
*,
expressions: Iterable[str],
params: f_params.FunctionParams,
io: f_params.PortDirection,
control_states: List[ControlState],
strict_zero_ios: bool,
) -> Tuple[List[str], List[str], List[str]]:
expression_matches: Iterable[Optional[Match]] = map(
functools.partial(re.fullmatch, IO_REGEX), expressions
)
valid_matches: List[Match] = []
invalid_expressions: List[str] = []
for expression, expression_match in zip(expressions, expression_matches):
(
invalid_expressions.append(expression)
if expression_match is None
else valid_matches.append(expression_match)
)
invalid_slicings: List[str] = []
invalid_names: List[str] = []
valid_names = frozenset(
params.inputs_full(strict_zero_ios)
if io == PortDirection.Input
else params.outputs
)
for match in valid_matches:
name = match.groupdict().get(NAME)
if name is None:
raise AssertionError("Input/output name validation error")
slicing = match.groupdict().get(SLICING)
if slicing is not None and re.fullmatch(LEGAL_SLICING, slicing) is None:
invalid_slicings.append(match.string)
if name in valid_names:
continue
elif all(state.name != name for state in control_states):
invalid_names.append(name)
return invalid_expressions, invalid_slicings, invalid_names
def update_ios(self, inputs: ArithmeticIODict, outputs: ArithmeticIODict) -> None:
if not isinstance(self.function_params, CustomFunction):
raise AssertionError("CustomFunction object expected.")
self.function_params.generate_ios(
inputs=inputs,
outputs=outputs,
)
SynthesisQuantumFunctionCall._validate_input_names(
params=self.function_params,
inputs=self.inputs_dict,
is_inverse=self.is_inverse,
control_states=self.control_states,
strict_zero_ios=self.strict_zero_ios,
)
SynthesisQuantumFunctionCall._validate_output_names(
params=self.function_params,
outputs=self.outputs_dict,
is_inverse=self.is_inverse,
control_states=self.control_states,
strict_zero_ios=self.strict_zero_ios,
)
def inverse(self) -> SynthesisQuantumFunctionCall:
call_kwargs = self.__dict__.copy()
call_kwargs["inputs"] = self.outputs_dict
call_kwargs["outputs"] = self.inputs_dict
call_kwargs["name"] = self.inverse_name(self.name)
call_kwargs["is_inverse"] = not self.is_inverse
return SynthesisQuantumFunctionCall(**call_kwargs)
@staticmethod
def inverse_name(name: str) -> str:
if name.endswith(INVERSE_SUFFIX):
return name[: -len(INVERSE_SUFFIX)]
return f"{name}{INVERSE_SUFFIX}"
def control(
self, control_state: ControlState, input_wire: WireName, output_wire: WireName
) -> SynthesisQuantumFunctionCall:
if (
control_state.name in self.inputs_dict
or control_state.name in self.outputs_dict
):
raise ClassiqValueError(
f"Control name: {control_state.name} already exists"
)
inputs, outputs = dict(self.inputs_dict), dict(self.outputs_dict)
inputs.update({control_state.name: input_wire})
outputs.update({control_state.name: output_wire})
call_kwargs = self.__dict__.copy()
call_kwargs["inputs"] = inputs
call_kwargs["outputs"] = outputs
call_kwargs["name"] = f"{self.name}_{control_state.name}"
call_kwargs["control_states"] = self.control_states + [control_state]
return SynthesisQuantumFunctionCall(**call_kwargs)
class Config:
extra = Extra.forbid
control_states: List[classiq.interface.generator.control_state.ControlState]
pydantic-field
¶
Call the controlled function with the given controlled states.
function: str
pydantic-field
required
¶
The function that is called
function_params: FunctionParams
pydantic-field
¶
The parameters necessary for defining the function
inouts: Mapping[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, classiq.interface.generator.quantum_function_call.WirePair]
pydantic-field
¶
A mapping from in/out name to the wires that connect to it
inputs: Union[Mapping[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue], classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue]
pydantic-field
¶
A mapping from the input name to the wire it connects to
is_inverse: bool
pydantic-field
¶
Call the function inverse.
name: ConstrainedStrValue
pydantic-field
¶
The name of the function instance. If not set, determined automatically.
outputs: Union[Mapping[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue], classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue]
pydantic-field
¶
A mapping from the output name to the wire it connects to
power: Union[classiq.interface.backend.pydantic_backend.ConstrainedStrValue, int]
pydantic-field
¶
Number of successive calls to the operation
release_by_inverse: bool
pydantic-field
¶
Release zero inputs in inverse call.
should_control: bool
pydantic-field
¶
False value indicates this call shouldn't be controlled even if the flow is controlled.
strict_zero_ios: bool
pydantic-field
¶
Enables automated qubit allocation for pre-determined zero inputs and allows automated qubit release when performing inversion. Setting this flag to False exposes zero inputs and outputs as regular functional registers, and shifts the responsibility to the user to manually manage qubit allocation and release.
__eq__(self, other)
special
¶
Return self==value.
Source code in classiq/interface/generator/quantum_function_call.py
def __eq__(self, other: Any) -> bool:
return (
isinstance(other, SynthesisQuantumFunctionCall) and self.name == other.name
)
__hash__(self)
special
¶
Return hash(self).
Source code in classiq/interface/generator/quantum_function_call.py
def __hash__(self) -> int:
return hash(self.name)
quantum_program
¶
QuantumProgram (VersionedModel, CircuitCodeInterface)
pydantic-model
¶
Source code in classiq/interface/generator/quantum_program.py
class QuantumProgram(VersionedModel, CircuitCodeInterface):
hardware_data: SynthesisHardwareData
initial_values: Optional[InitialConditions]
data: GeneratedCircuitData
model: ExecutionModel
transpiled_circuit: Optional[TranspiledCircuitData]
creation_time: str = pydantic.Field(default_factory=datetime.utcnow().isoformat)
synthesis_duration: Optional[SynthesisStepDurations]
debug_info: Optional[List[FunctionDebugInfo]]
program_id: str = pydantic.Field(default_factory=get_uuid_as_str)
def _hardware_agnostic_program_code(self) -> CodeAndSyntax:
circuit_code = self.program_circuit.get_code_by_priority()
if circuit_code is not None:
return circuit_code
raise ClassiqMissingOutputFormatError(
missing_formats=list(INSTRUCTION_SET_TO_FORMAT.values())
)
def _default_program_code(self) -> CodeAndSyntax:
if self.hardware_data.backend_data is None:
return self._hardware_agnostic_program_code()
backend_provider = self.hardware_data.backend_data.hw_provider
instruction_set: QuantumInstructionSet = VENDOR_TO_INSTRUCTION_SET.get(
backend_provider, DEFAULT_INSTRUCTION_SET
)
return self.program_circuit.get_code(instruction_set), instruction_set
def to_base_program(self) -> quantum_code.QuantumBaseCode:
code, syntax = self._default_program_code()
return quantum_code.QuantumBaseCode(code=code, syntax=syntax)
def to_program(
self,
initial_values: Optional[InitialConditions] = None,
instruction_set: Optional[QuantumInstructionSet] = None,
) -> quantum_code.QuantumCode:
initial_values = initial_values or self.initial_values
if instruction_set is not None:
code, syntax = (
self.program_circuit.get_code(instruction_set),
instruction_set,
)
else:
code, syntax = self._default_program_code()
if initial_values is not None:
registers_initialization = self.get_registers_initialization(
initial_values=initial_values
)
else:
registers_initialization = None
return quantum_code.QuantumCode(
code=code,
syntax=syntax,
output_qubits_map=self.data.qubit_mapping.physical_outputs,
registers_initialization=registers_initialization,
synthesis_execution_data=self.data.execution_data,
)
def _get_initialization_qubits(self, name: str) -> Tuple[int, ...]:
qubits = self.data.qubit_mapping.logical_inputs.get(name)
if qubits is None:
raise ClassiqStateInitializationError(
f"Cannot initialize register {name}, it does not appear in circuit inputs"
)
return qubits
def get_registers_initialization(
self, initial_values: InitialConditions
) -> Dict[RegisterName, RegisterInitialization]:
return {
name: RegisterInitialization(
name=name,
qubits=list(self._get_initialization_qubits(name)),
initial_condition=init_value,
)
for name, init_value in initial_values.items()
}
def save_results(self, filename: Optional[Union[str, Path]] = None) -> None:
"""
Saves quantum program results as json.
Parameters:
filename (Union[str, Path]): Optional, path + filename of file.
If filename supplied add `.json` suffix.
Returns:
None
"""
if filename is None:
filename = f"synthesised_circuit_{self.creation_time}.json"
with open(filename, "w") as file:
file.write(self.json(indent=4))
@classmethod
def from_qprog(cls, qprog: str) -> "QuantumProgram":
return cls.parse_raw(qprog)
@property
def _can_use_transpiled_code(self) -> bool:
return self.data.execution_data is None
@property
def program_circuit(self) -> CircuitCodeInterface:
return (
self.transpiled_circuit
if self.transpiled_circuit and self._can_use_transpiled_code
else self
)
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
save_results(self, filename=None)
¶
Saves quantum program results as json.
!!! parameters
filename (Union[str, Path]): Optional, path + filename of file.
If filename supplied add .json
suffix.
!!! returns
None
Source code in classiq/interface/generator/quantum_program.py
def save_results(self, filename: Optional[Union[str, Path]] = None) -> None:
"""
Saves quantum program results as json.
Parameters:
filename (Union[str, Path]): Optional, path + filename of file.
If filename supplied add `.json` suffix.
Returns:
None
"""
if filename is None:
filename = f"synthesised_circuit_{self.creation_time}.json"
with open(filename, "w") as file:
file.write(self.json(indent=4))
range_mixer
¶
RangeMixer (FunctionParams)
pydantic-model
¶
Mixing a fixed point number variable between a given lower and upper bounds. I.e. after applying this function the variable will hold a superposition of all the valid values.
Source code in classiq/interface/generator/range_mixer.py
class RangeMixer(FunctionParams):
"""
Mixing a fixed point number variable between a given lower and upper bounds.
I.e. after applying this function the variable will hold a
superposition of all the valid values.
"""
data_reg_input: RegisterArithmeticInfo = pydantic.Field(
description="The input variable to mix."
)
lower_bound_reg_input: RegisterOrConst = pydantic.Field(
description="Fixed number or variable that define the lower bound for"
" the mixing operation. In case of a fixed number bound, the value"
" must be positive."
)
upper_bound_reg_input: RegisterOrConst = pydantic.Field(
description="Fixed number or variable that define the upper bound for"
" the mixing operation. In case of a fixed number bound, the value"
" must be positive."
)
mixer_parameter: ParameterFloatType = pydantic.Field(
description="The parameter used for rotation gates in the mixer.",
is_exec_param=True,
)
def _create_ios(self) -> None:
self._inputs = {DATA_REG_INPUT_NAME: self.data_reg_input}
self._outputs = {DATA_REG_OUTPUT_NAME: self.data_reg_input}
if isinstance(self.lower_bound_reg_input, RegisterArithmeticInfo):
self._inputs[LOWER_BOUND_REG_INPUT_NAME] = self.lower_bound_reg_input
self._outputs[LOWER_BOUND_REG_OUTPUT_NAME] = self.lower_bound_reg_input
if isinstance(self.upper_bound_reg_input, RegisterArithmeticInfo):
self._inputs[UPPER_BOUND_REG_INPUT_NAME] = self.upper_bound_reg_input
self._outputs[UPPER_BOUND_REG_OUTPUT_NAME] = self.upper_bound_reg_input
data_reg_input: RegisterArithmeticInfo
pydantic-field
required
¶
The input variable to mix.
lower_bound_reg_input: Union[classiq.interface.generator.arith.register_user_input.RegisterArithmeticInfo, float]
pydantic-field
required
¶
Fixed number or variable that define the lower bound for the mixing operation. In case of a fixed number bound, the value must be positive.
mixer_parameter: Union[float, str]
pydantic-field
required
¶
The parameter used for rotation gates in the mixer.
upper_bound_reg_input: Union[classiq.interface.generator.arith.register_user_input.RegisterArithmeticInfo, float]
pydantic-field
required
¶
Fixed number or variable that define the upper bound for the mixing operation. In case of a fixed number bound, the value must be positive.
standard_gates
special
¶
controlled_standard_gates
¶
C3XGate (ControlledGateWithState)
pydantic-model
¶
The X Gate controlled on 3 qubits
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class C3XGate(ControlledGateWithState): # type: ignore[misc]
"""
The X Gate controlled on 3 qubits
"""
_name: str = "mcx"
num_ctrl_qubits: Literal[3] = pydantic.Field(default=3)
def get_power_order(self) -> int:
return 2
C4XGate (ControlledGateWithState)
pydantic-model
¶
The X Gate controlled on 4 qubits
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class C4XGate(ControlledGateWithState): # type: ignore[misc]
"""
The X Gate controlled on 4 qubits
"""
_name: str = "mcx"
num_ctrl_qubits: Literal[4] = pydantic.Field(default=4)
def get_power_order(self) -> int:
return 2
CCXGate (ControlledGateWithState)
pydantic-model
¶
The Double Controlled-X Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CCXGate(ControlledGateWithState): # type: ignore[misc]
"""
The Double Controlled-X Gate
"""
num_ctrl_qubits: Literal[2] = pydantic.Field(default=2)
def get_power_order(self) -> int:
return 2
CHGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-H Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CHGate(ControlledGateWithState): # type: ignore[misc]
"""
The Controlled-H Gate
"""
def get_power_order(self) -> int:
return 2
CPhaseGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-Phase Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CPhaseGate(ControlledGateWithState, angles=["theta"]): # type: ignore[misc]
"""
The Controlled-Phase Gate
"""
_name: str = "cp"
CRXGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-RX Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CRXGate(ControlledGateWithState, angles=["theta"]): # type: ignore[misc]
"""
The Controlled-RX Gate
"""
CRYGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-RY Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CRYGate(ControlledGateWithState, angles=["theta"]): # type: ignore[misc]
"""
The Controlled-RY Gate
"""
CRZGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-RZ Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CRZGate(ControlledGateWithState, angles=["theta"]): # type: ignore[misc]
"""
The Controlled-RZ Gate
"""
CSXGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-SX Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CSXGate(ControlledGateWithState): # type: ignore[misc]
"""
The Controlled-SX Gate
"""
def get_power_order(self) -> int:
return 4
CXGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-X Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CXGate(ControlledGateWithState): # type: ignore[misc]
"""
The Controlled-X Gate
"""
num_ctrl_qubits: Literal[1] = pydantic.Field(default=1)
def get_power_order(self) -> int:
return 2
CYGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-Y Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CYGate(ControlledGateWithState): # type: ignore[misc]
"""
The Controlled-Y Gate
"""
def get_power_order(self) -> int:
return 2
CZGate (ControlledGateWithState)
pydantic-model
¶
The Controlled-Z Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class CZGate(ControlledGateWithState): # type: ignore[misc]
"""
The Controlled-Z Gate
"""
def get_power_order(self) -> int:
return 2
ControlledGate (_StandardGate)
pydantic-model
¶
Base model for controlled Gates
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class ControlledGate(_StandardGate): # type: ignore[misc]
"""
Base model for controlled Gates
"""
num_ctrl_qubits: pydantic.PositiveInt = pydantic.Field(
default=DEFAULT_NUM_CTRL_QUBITS
)
def _create_ios(self) -> None:
_StandardGate._create_ios(self)
control = RegisterUserInput(
name=CONTROLLED_GATE_CONTROL, size=self.num_ctrl_qubits
)
self._inputs[CONTROLLED_GATE_CONTROL] = control
self._outputs[CONTROLLED_GATE_CONTROL] = control
def to_control_state(self) -> ControlState:
return ControlState(
name=CONTROLLED_GATE_CONTROL, num_ctrl_qubits=self.num_ctrl_qubits
)
ControlledGateWithState (ControlledGate)
pydantic-model
¶
Base model for controlled Gates with control over the controlled_state
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class ControlledGateWithState(ControlledGate): # type: ignore[misc]
"""
Base model for controlled Gates with control over the controlled_state
"""
ctrl_state: CtrlState = pydantic.Field(
description="The control state in decimal or as a bit string (e.g. '1011'). If not specified, the control "
"state is 2**num_ctrl_qubits - 1.\n"
"The gate will be performed if the state of the control qubits matches the control state"
)
@pydantic.validator("ctrl_state", always=True)
def _validate_ctrl_state(
cls, ctrl_state: CtrlState, values: Dict[str, Any]
) -> CtrlState:
num_ctrl_qubits: int = values.get("num_ctrl_qubits", DEFAULT_NUM_CTRL_QUBITS)
ctrl_state = ctrl_state if ctrl_state is not None else "1" * num_ctrl_qubits
if isinstance(ctrl_state, str) and len(ctrl_state) != num_ctrl_qubits:
raise ClassiqValueError(
f"Invalid control state: {ctrl_state!r}. "
f"Expected {num_ctrl_qubits} qubits"
)
elif isinstance(ctrl_state, int) and ctrl_state >= 2**num_ctrl_qubits:
raise ClassiqValueError(
f"Invalid control state: {ctrl_state}. "
f"Expected value between 0 and {2**num_ctrl_qubits-1}"
)
return ctrl_state
def to_control_state(self) -> ControlState:
ctrl_state_str = (
_num_to_control_string(self.ctrl_state, self.num_ctrl_qubits)
if isinstance(self.ctrl_state, int)
else self.ctrl_state
)
return ControlState(name=CONTROLLED_GATE_CONTROL, ctrl_state=ctrl_state_str)
ctrl_state: Union[pydantic.types.StrictStr, pydantic.types.NonNegativeInt]
pydantic-field
¶The control state in decimal or as a bit string (e.g. '1011'). If not specified, the control state is 2**num_ctrl_qubits - 1. The gate will be performed if the state of the control qubits matches the control state
MCPhaseGate (ControlledGate)
pydantic-model
¶
The Controlled-Phase Gate
Source code in classiq/interface/generator/standard_gates/controlled_standard_gates.py
class MCPhaseGate(ControlledGate, angles=["lam"]): # type: ignore[misc]
"""
The Controlled-Phase Gate
"""
_name: str = "mcphase"
standard_angle_metaclass
¶
MyMetaAngledClass (type)
¶
Source code in classiq/interface/generator/standard_gates/standard_angle_metaclass.py
class MyMetaAngledClass(type):
def __new__(cls, name: str, bases: tuple, namespace: dict, **kwargs: Any) -> type:
a_totally_new_namespace = cls._create_new_namespace(namespace, **kwargs)
return type.__new__(cls, name, bases, a_totally_new_namespace)
@staticmethod
def _create_new_namespace(namespace: dict, **kwargs: Any) -> dict:
angles = kwargs.get("angles", [])
annotations = {angle: ParameterFloatType for angle in angles}
fields = {
angle: pydantic.fields.FieldInfo(is_exec_param=True) for angle in angles
}
original_annotations = namespace.get("__annotations__", {})
return {
**namespace,
**fields,
**{"__annotations__": {**original_annotations, **annotations}},
}
__new__(cls, name, bases, namespace, **kwargs)
special
staticmethod
¶Create and return a new object. See help(type) for accurate signature.
Source code in classiq/interface/generator/standard_gates/standard_angle_metaclass.py
def __new__(cls, name: str, bases: tuple, namespace: dict, **kwargs: Any) -> type:
a_totally_new_namespace = cls._create_new_namespace(namespace, **kwargs)
return type.__new__(cls, name, bases, a_totally_new_namespace)
MyMetaAngledClassModel (ModelMetaclass, MyMetaAngledClass)
¶
Source code in classiq/interface/generator/standard_gates/standard_angle_metaclass.py
class MyMetaAngledClassModel(PydanticMetaClass, MyMetaAngledClass):
def __new__(
cls, name: str, bases: tuple, namespace: dict, **kwargs: Any
) -> PydanticMetaClass:
# First, populate the namespace (specifically, "__annotations__") according to `MyMetaAngledClass` with the angles defined
namespace = MyMetaAngledClass._create_new_namespace(namespace, **kwargs)
# Clean `kwargs` afterwards so that it won't pass again to the final class
if "angles" in kwargs:
kwargs.pop("angles")
# Next, continue with pydantic's flow
return PydanticMetaClass.__new__(cls, name, bases, namespace, **kwargs)
__new__(cls, name, bases, namespace, **kwargs)
special
staticmethod
¶Create and return a new object. See help(type) for accurate signature.
Source code in classiq/interface/generator/standard_gates/standard_angle_metaclass.py
def __new__(
cls, name: str, bases: tuple, namespace: dict, **kwargs: Any
) -> PydanticMetaClass:
# First, populate the namespace (specifically, "__annotations__") according to `MyMetaAngledClass` with the angles defined
namespace = MyMetaAngledClass._create_new_namespace(namespace, **kwargs)
# Clean `kwargs` afterwards so that it won't pass again to the final class
if "angles" in kwargs:
kwargs.pop("angles")
# Next, continue with pydantic's flow
return PydanticMetaClass.__new__(cls, name, bases, namespace, **kwargs)
standard_angled_gates
¶
PhaseGate (_StandardGate)
pydantic-model
¶
Add relative phase of lambda
Source code in classiq/interface/generator/standard_gates/standard_angled_gates.py
class PhaseGate(_StandardGate, angles=["theta"]): # type: ignore[misc]
"""
Add relative phase of lambda
"""
_name: str = "p"
RGate (_StandardGate)
pydantic-model
¶
Rotation by theta about the cos(phi)X + sin(phi)Y axis
Source code in classiq/interface/generator/standard_gates/standard_angled_gates.py
class RGate(_StandardGate, angles=["theta", "phi"]): # type: ignore[misc]
"""
Rotation by theta about the cos(phi)X + sin(phi)Y axis
"""
RXGate (_StandardGate)
pydantic-model
¶
Rotation by theta about the X axis
Source code in classiq/interface/generator/standard_gates/standard_angled_gates.py
class RXGate(_StandardGate, angles=["theta"]): # type: ignore[misc]
"""
Rotation by theta about the X axis
"""
RXXGate (_DoubleRotationGate)
pydantic-model
¶
Rotation by theta about the XX axis
Source code in classiq/interface/generator/standard_gates/standard_angled_gates.py
class RXXGate(_DoubleRotationGate): # type: ignore[misc]
"""
Rotation by theta about the XX axis
"""
RYGate (_StandardGate)
pydantic-model
¶
Rotation by theta about the Y axis
Source code in classiq/interface/generator/standard_gates/standard_angled_gates.py
class RYGate(_StandardGate, angles=["theta"]): # type: ignore[misc]
"""
Rotation by theta about the Y axis
"""
RYYGate (_DoubleRotationGate)
pydantic-model
¶
Rotation by theta about the YY axis
Source code in classiq/interface/generator/standard_gates/standard_angled_gates.py
class RYYGate(_DoubleRotationGate): # type: ignore[misc]
"""
Rotation by theta about the YY axis
"""
RZGate (_StandardGate)
pydantic-model
¶
Rotation by phi about the Z axis
Source code in classiq/interface/generator/standard_gates/standard_angled_gates.py
class RZGate(_StandardGate, angles=["phi"]): # type: ignore[misc]
"""
Rotation by phi about the Z axis
"""
RZZGate (_DoubleRotationGate)
pydantic-model
¶
Rotation by theta about the ZZ axis
Source code in classiq/interface/generator/standard_gates/standard_angled_gates.py
class RZZGate(_DoubleRotationGate): # type: ignore[misc]
"""
Rotation by theta about the ZZ axis
"""
standard_gates
¶
HGate (UncontrolledStandardGate)
pydantic-model
¶
creates a Hadamard gate
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class HGate(UncontrolledStandardGate): # type: ignore[misc]
"""
creates a Hadamard gate
"""
def get_power_order(self) -> int:
return 2
IGate (UncontrolledStandardGate)
pydantic-model
¶
creates the identity gate
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class IGate(UncontrolledStandardGate): # type: ignore[misc]
"""
creates the identity gate
"""
_name: str = "id"
def get_power_order(self) -> int:
return 1
SGate (UncontrolledStandardGate)
pydantic-model
¶
Z**0.5
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class SGate(UncontrolledStandardGate): # type: ignore[misc]
"""
Z**0.5
"""
def get_power_order(self) -> int:
return 4
SXGate (UncontrolledStandardGate)
pydantic-model
¶
X**0.5
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class SXGate(UncontrolledStandardGate): # type: ignore[misc]
"""
X**0.5
"""
def get_power_order(self) -> int:
return 4
SXdgGate (UncontrolledStandardGate)
pydantic-model
¶
creates the inverse SX gate
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class SXdgGate(UncontrolledStandardGate): # type: ignore[misc]
"""
creates the inverse SX gate
"""
def get_power_order(self) -> int:
return 4
SdgGate (UncontrolledStandardGate)
pydantic-model
¶
creates the inverse S gate
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class SdgGate(UncontrolledStandardGate): # type: ignore[misc]
"""
creates the inverse S gate
"""
def get_power_order(self) -> int:
return 4
SwapGate (UncontrolledStandardGate)
pydantic-model
¶
Swaps between two qubit states
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class SwapGate(UncontrolledStandardGate): # type: ignore[misc]
"""
Swaps between two qubit states
"""
_num_target_qubits: pydantic.PositiveInt = pydantic.PrivateAttr(default=2)
def get_power_order(self) -> int:
return 2
TGate (UncontrolledStandardGate)
pydantic-model
¶
Z**0.25
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class TGate(UncontrolledStandardGate): # type: ignore[misc]
"""
Z**0.25
"""
def get_power_order(self) -> int:
return 8
TdgGate (UncontrolledStandardGate)
pydantic-model
¶
creates the inverse T gate
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class TdgGate(UncontrolledStandardGate): # type: ignore[misc]
"""
creates the inverse T gate
"""
def get_power_order(self) -> int:
return 8
XGate (UncontrolledStandardGate)
pydantic-model
¶
creates a X gate
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class XGate(UncontrolledStandardGate): # type: ignore[misc]
"""
creates a X gate
"""
def get_power_order(self) -> int:
return 2
YGate (UncontrolledStandardGate)
pydantic-model
¶
creates a Y gate
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class YGate(UncontrolledStandardGate): # type: ignore[misc]
"""
creates a Y gate
"""
def get_power_order(self) -> int:
return 2
ZGate (UncontrolledStandardGate)
pydantic-model
¶
create a Z gate
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class ZGate(UncontrolledStandardGate): # type: ignore[misc]
"""
create a Z gate
"""
def get_power_order(self) -> int:
return 2
iSwapGate (UncontrolledStandardGate)
pydantic-model
¶
Swaps between two qubit states and add phase of i to the amplitudes of |01> and |10>
Source code in classiq/interface/generator/standard_gates/standard_gates.py
class iSwapGate(UncontrolledStandardGate): # type: ignore[misc] # noqa: N801
"""
Swaps between two qubit states and add phase of i to the amplitudes of |01> and |10>
"""
_num_target_qubits: pydantic.PositiveInt = pydantic.PrivateAttr(default=2)
def get_power_order(self) -> int:
return 4
u_gate
¶
UGate (FunctionParams)
pydantic-model
¶
Matrix representation:
U(gam, phi,theta, lam) = e^(igam) * cos(theta/2) & -e^(ilam)sin(theta/2) \ e^(iphi)sin(theta/2) & e^(i(phi+lam))*cos(theta/2) \
Source code in classiq/interface/generator/standard_gates/u_gate.py
class UGate(function_params.FunctionParams):
"""
Matrix representation:
U(gam, phi,theta, lam) =
e^(i*gam) *
cos(theta/2) & -e^(i*lam)*sin(theta/2) \\
e^(i*phi)*sin(theta/2) & e^(i*(phi+lam))*cos(theta/2) \\
"""
theta: ParameterFloatType = pydantic.Field(
description="Angle to rotate by the Y-axis.",
is_exec_param=True,
)
phi: ParameterFloatType = pydantic.Field(
description="First angle to rotate by the Z-axis.",
is_exec_param=True,
)
lam: ParameterFloatType = pydantic.Field(
description="Second angle to rotate by the Z-axis.",
is_exec_param=True,
)
gam: ParameterFloatType = pydantic.Field(
description="Angle to apply phase gate by.",
is_exec_param=True,
)
_inputs = pydantic.PrivateAttr(
default={
DEFAULT_STANDARD_GATE_ARG_NAME: RegisterUserInput(
name=DEFAULT_STANDARD_GATE_ARG_NAME, size=1
)
}
)
_outputs = pydantic.PrivateAttr(
default={
DEFAULT_STANDARD_GATE_ARG_NAME: RegisterUserInput(
name=DEFAULT_STANDARD_GATE_ARG_NAME, size=1
)
}
)
@property
def is_parametric(self) -> bool:
return not all(
isinstance(getattr(self, angle), (float, int)) for angle in self._params
)
gam: Union[float, str]
pydantic-field
required
¶Angle to apply phase gate by.
lam: Union[float, str]
pydantic-field
required
¶Second angle to rotate by the Z-axis.
phi: Union[float, str]
pydantic-field
required
¶First angle to rotate by the Z-axis.
theta: Union[float, str]
pydantic-field
required
¶Angle to rotate by the Y-axis.
state_preparation
special
¶
computational_basis_state_preparation
¶
ComputationalBasisStatePreparation (StatePreparationABC)
pydantic-model
¶
Source code in classiq/interface/generator/state_preparation/computational_basis_state_preparation.py
class ComputationalBasisStatePreparation(StatePreparationABC):
computational_state: PydanticNonEmptyString = pydantic.Field(
description="binary computational state to create"
)
@pydantic.validator("computational_state")
def _validate_computational_state(
cls, computational_state: PydanticNonEmptyString
) -> PydanticNonEmptyString:
ControlState.validate_control_string(computational_state)
return computational_state
@property
def num_state_qubits(self) -> int:
return len(self.computational_state)
def get_power_order(self) -> int:
return 2
computational_state: ConstrainedStrValue
pydantic-field
required
¶binary computational state to create
distributions
¶
GaussianMixture (BaseModel)
pydantic-model
¶
Source code in classiq/interface/generator/state_preparation/distributions.py
class GaussianMixture(pydantic.BaseModel):
gaussian_moment_list: Tuple[GaussianMoments, ...]
num_qubits: pydantic.PositiveInt = pydantic.Field(
description="Number of qubits for the provided state."
)
class Config:
frozen = True
num_qubits: PositiveInt
pydantic-field
required
¶Number of qubits for the provided state.
state_preparation
¶
StatePreparation (StatePreparationABC)
pydantic-model
¶
Source code in classiq/interface/generator/state_preparation/state_preparation.py
class StatePreparation(StatePreparationABC):
amplitudes: Optional[Amplitudes] = pydantic.Field(
description="vector of probabilities", default=None
)
probabilities: Optional[Probabilities] = pydantic.Field(
description="vector of amplitudes", default=None
)
error_metric: Dict[Metrics, NonNegativeFloatRange] = pydantic.Field(
default_factory=lambda: {
Metrics.L2: NonNegativeFloatRange(lower_bound=0, upper_bound=1e-4)
}
)
# The order of validations is important: amplitudes, probabilities, error_metric
@pydantic.validator("amplitudes", always=True, pre=True)
def _initialize_amplitudes(
cls, amplitudes: Optional[FlexibleAmplitudes]
) -> Optional[Amplitudes]:
if amplitudes is None:
return None
amplitudes = np.array(amplitudes).squeeze()
if amplitudes.ndim == 1:
return validate_amplitudes(tuple(amplitudes))
raise ClassiqValueError(
"Invalid amplitudes were given, please ensure the amplitude is a vector of float in the form of either tuple or list or numpy array"
)
@pydantic.validator("probabilities", always=True, pre=True)
def _initialize_probabilities(
cls, probabilities: Optional[FlexibleProbabilities]
) -> Optional[Union[PMF, GaussianMixture, dict]]:
if probabilities is None:
return None
if isinstance(probabilities, Probabilities.__args__): # type: ignore[attr-defined]
return probabilities
if isinstance(probabilities, dict): # a pydantic object
return probabilities
probabilities = np.array(probabilities).squeeze()
if probabilities.ndim == 1:
return PMF(pmf=probabilities.tolist())
raise ClassiqValueError(
"Invalid probabilities were given, please ensure the probabilities is a vector of float in the form of either tuple or list or numpy array"
)
@pydantic.validator("error_metric", always=True, pre=True)
def _validate_error_metric(
cls, error_metric: Dict[Metrics, NonNegativeFloatRange], values: Dict[str, Any]
) -> Dict[Metrics, NonNegativeFloatRange]:
if not values.get("amplitudes"):
return error_metric
unsupported_metrics = {
Metrics(metric).value
for metric in error_metric
if not Metrics(metric).supports_amplitudes
}
if unsupported_metrics:
raise ClassiqValueError(
f"{unsupported_metrics} are not supported for amplitude preparation"
)
return error_metric
@pydantic.root_validator
def _validate_either_probabilities_or_amplitudes(
cls,
values: Dict[str, Any],
) -> Optional[Union[PMF, GaussianMixture, dict]]:
amplitudes = values.get("amplitudes")
probabilities = values.get("probabilities")
if amplitudes is not None and probabilities is not None:
raise ClassiqValueError(
"StatePreparation can't get both probabilities and amplitudes"
)
return values
@property
def num_state_qubits(self) -> int:
distribution = self.probabilities or self.amplitudes
if distribution is None:
raise ClassiqValueError("Must have either probabilities or amplitudes")
return num_of_qubits(distribution)
state_propagator
¶
StatePropagator (FunctionParams)
pydantic-model
¶
Creates a quantum circuit that propagates the start state vector to the end state vector, both are assumed to be pure states. The default start state vector is |000...0>.
Source code in classiq/interface/generator/state_propagator.py
class StatePropagator(function_params.FunctionParams):
"""
Creates a quantum circuit that propagates the start state vector to the end state vector,
both are assumed to be pure states.
The default start state vector is |000...0>.
"""
end_state_vector: List[Complex] = pydantic.Field(
description="The desired state vector at the end of the operator."
" Must be of size 2**num_qubits. Does not have to be "
"normalized"
)
start_state_vector: List[Complex] = pydantic.Field(
default_factory=list,
description="The state vector at the input of the operator."
" Must be of size 2**num_qubits. Does not have to be"
" normalized",
)
@pydantic.validator("start_state_vector", always=True)
def validate_start_state(
cls, start_state_vector: List[Complex], values: Dict[str, Any]
) -> List[Complex]:
end_state_vector = values.get("end_state_vector")
if end_state_vector is None:
raise ClassiqValueError(
"Cannot validate start_start_vector without end_state_vector"
)
num_qubits = cls._num_qubits(end_state_vector)
if len(start_state_vector) == 0:
default_start_state_vector = [Complex(0.0) for _ in range(2**num_qubits)]
default_start_state_vector[0] = Complex(1.0)
start_state_vector = default_start_state_vector
if len(start_state_vector) != len(end_state_vector):
raise ClassiqValueError(
"Start and end state vectors are of non-equal length"
)
return start_state_vector
@staticmethod
def _num_qubits(vector: List[Complex]) -> int:
return int(np.log2(len(vector)))
def _create_ios(self) -> None:
self._inputs = {
DEFAULT_INPUT_NAME: RegisterArithmeticInfo(
size=self._num_qubits(self.start_state_vector)
)
}
self._outputs = {
DEFAULT_OUTPUT_NAME: RegisterArithmeticInfo(
size=self._num_qubits(self.end_state_vector)
)
}
end_state_vector: List[classiq.interface.generator.complex_type.Complex]
pydantic-field
required
¶
The desired state vector at the end of the operator. Must be of size 2**num_qubits. Does not have to be normalized
start_state_vector: List[classiq.interface.generator.complex_type.Complex]
pydantic-field
¶
The state vector at the input of the operator. Must be of size 2**num_qubits. Does not have to be normalized
types
special
¶
struct_declaration
¶
StructDeclaration (HashableASTNode)
pydantic-model
¶
Source code in classiq/interface/generator/types/struct_declaration.py
class StructDeclaration(HashableASTNode):
name: str
variables: Dict[str, ConcreteClassicalType] = pydantic.Field(
default_factory=dict,
description="Dictionary of variable names and their classical types",
)
BUILTIN_STRUCT_DECLARATIONS: ClassVar[Dict[str, "StructDeclaration"]] = {}
def validate_fields(self, fields: Mapping[str, Any]) -> None:
expected_field_names = list(self.variables.keys())
received_field_names = list(fields.keys())
if set(expected_field_names) != set(received_field_names):
raise ClassiqValueError(
f"Invalid fields for {self.name} instance. Expected fields "
f"{expected_field_names}, got {received_field_names}"
)
variables: Dict[str, Annotated[Union[classiq.interface.generator.functions.classical_type.Integer, classiq.interface.generator.functions.classical_type.Real, classiq.interface.generator.functions.classical_type.Bool, classiq.interface.generator.functions.classical_type.ClassicalList, classiq.interface.generator.functions.classical_type.Pauli, classiq.interface.generator.functions.classical_type.StructMetaType, classiq.interface.generator.functions.classical_type.Struct, classiq.interface.generator.functions.classical_type.ClassicalArray, classiq.interface.generator.functions.classical_type.VQEResult, classiq.interface.generator.functions.classical_type.Histogram, classiq.interface.generator.functions.classical_type.Estimation, classiq.interface.generator.functions.classical_type.LadderOperator, classiq.interface.generator.functions.classical_type.IQAERes], FieldInfo(default=PydanticUndefined, discriminator='kind', extra={})]]
pydantic-field
¶Dictionary of variable names and their classical types
ucc
¶
UCC (ChemistryFunctionParams)
pydantic-model
¶
Ucc ansatz
Source code in classiq/interface/generator/ucc.py
class UCC(ChemistryFunctionParams):
"""
Ucc ansatz
"""
use_naive_evolution: bool = pydantic.Field(
default=False, description="Whether to evolve the operator naively"
)
excitations: EXCITATIONS_TYPE = pydantic.Field(
default_factory=default_excitation_factory,
description="type of excitation operators in the UCC ansatz",
)
max_depth: Optional[pydantic.PositiveInt] = pydantic.Field(
default=None,
description="Maximum depth of the generated quantum circuit ansatz",
)
parameter_prefix: str = pydantic.Field(
default="param_",
description="Prefix for the generated parameters",
)
@pydantic.validator("excitations")
def _validate_excitations(cls, excitations: EXCITATIONS_TYPE) -> EXCITATIONS_TYPE:
if isinstance(excitations, int):
if excitations not in _EXCITATIONS_DICT.values():
raise ClassiqValueError(
f"possible values of excitations are {list(_EXCITATIONS_DICT.values())}"
)
excitations = [excitations]
elif isinstance(excitations, Iterable):
excitations = list(excitations) # type: ignore[assignment]
if all(isinstance(idx, int) for idx in excitations):
if any(idx not in _EXCITATIONS_DICT.values() for idx in excitations):
raise ClassiqValueError(
f"possible values of excitations are {list(_EXCITATIONS_DICT.values())}"
)
elif all(isinstance(idx, str) for idx in excitations):
if any(idx not in _EXCITATIONS_DICT.keys() for idx in excitations):
raise ClassiqValueError(
f"possible values of excitations are {list(_EXCITATIONS_DICT.keys())}"
)
excitations = sorted(_EXCITATIONS_DICT[idx] for idx in excitations) # type: ignore[index]
else:
raise ClassiqValueError(
"excitations must be of the same type (all str or all int)"
)
return excitations
excitations: Union[str, int, Iterable[int], Iterable[str]]
pydantic-field
¶
type of excitation operators in the UCC ansatz
max_depth: PositiveInt
pydantic-field
¶
Maximum depth of the generated quantum circuit ansatz
parameter_prefix: str
pydantic-field
¶
Prefix for the generated parameters
use_naive_evolution: bool
pydantic-field
¶
Whether to evolve the operator naively
unitary_gate
¶
UnitaryGate (FunctionParams)
pydantic-model
¶
Creates a circuit implementing a specified 2n * 2n unitary transformation.
Source code in classiq/interface/generator/unitary_gate.py
class UnitaryGate(function_params.FunctionParams):
"""
Creates a circuit implementing a specified 2**n * 2**n unitary transformation.
"""
# TODO - add support to numpy array-like (requires custom pydantic type definition)
data: DataArray = pydantic.Field(
description="A 2**n * 2**n (n positive integer) unitary matrix."
)
# TODO - decide if to include assertion on the unitarity of the matrix. It is already done in Qiskit and could be computationally expensive
@pydantic.validator("data")
def validate_data(cls, data: DataArray) -> DataArray:
data_np = np.array(data, dtype=object)
if data_np.ndim != 2:
raise ClassiqValueError("Data must me two dimensional")
if data_np.shape[0] != data_np.shape[1]:
raise ClassiqValueError("Matrix must be square")
if not np.mod(np.log2(data_np.shape[0]), 1) == 0:
raise ClassiqValueError(
"Matrix dimensions must be an integer exponent of 2"
)
return data
@property
def num_target_qubits(self) -> int:
return int(np.log2(len(self.data)))
def _create_ios(self) -> None:
self._inputs = {
UNITARY_GATE_INPUT: RegisterArithmeticInfo(size=self.num_target_qubits)
}
self._outputs = {
UNITARY_GATE_OUTPUT: RegisterArithmeticInfo(size=self.num_target_qubits)
}
data: List[List[Union[classiq.interface.generator.complex_type.Complex, float, int]]]
pydantic-field
required
¶
A 2n * 2n (n positive integer) unitary matrix.
user_defined_function_params
¶
CustomFunction (FunctionParams)
pydantic-model
¶
A user-defined custom function parameters object.
Source code in classiq/interface/generator/user_defined_function_params.py
class CustomFunction(FunctionParams):
"""
A user-defined custom function parameters object.
"""
_name: str = pydantic.PrivateAttr(default="")
input_decls: ArithmeticIODict = pydantic.Field(
default_factory=dict,
description="A mapping from the inputs names to the registers information. should be identical to the register defined in the function creation.",
)
output_decls: ArithmeticIODict = pydantic.Field(
default_factory=dict,
description="A mapping from the outputs names to the registers information. should be identical to the register defined in the function creation.",
)
def _create_ios(self) -> None:
self._inputs = self.input_decls
self._outputs = self.output_decls
def generate_ios(
self,
inputs: Mapping[str, RegisterArithmeticInfo],
outputs: Mapping[str, RegisterArithmeticInfo],
) -> None:
self._inputs = dict(inputs)
self._outputs = dict(outputs)
@property
def name(self) -> str:
return self._name
def set_name(self, name: str) -> None:
self._name = name
def __hash__(self) -> int:
return hash((super().__hash__(), self.name))
input_decls: Dict[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, classiq.interface.generator.arith.register_user_input.RegisterArithmeticInfo]
pydantic-field
¶
A mapping from the inputs names to the registers information. should be identical to the register defined in the function creation.
output_decls: Dict[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue, classiq.interface.generator.arith.register_user_input.RegisterArithmeticInfo]
pydantic-field
¶
A mapping from the outputs names to the registers information. should be identical to the register defined in the function creation.
validations
special
¶
flow_graph
¶
Wire
dataclass
¶
Wire(start: Optional[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue] = None, end: Optional[classiq.interface.helpers.custom_pydantic_types.ConstrainedStrValue] = None)
Source code in classiq/interface/generator/validations/flow_graph.py
@dataclass
class Wire:
start: Optional[PydanticNonEmptyString] = None
end: Optional[PydanticNonEmptyString] = None
grover
special
¶
grover_modelling_params
¶
GroverParams (BaseModel)
pydantic-model
¶
Source code in classiq/interface/grover/grover_modelling_params.py
class GroverParams(BaseModel):
oracle: ArithmeticOracle = pydantic.Field(
description="An arithmatic oracle for the grover search."
)
num_reps: int = pydantic.Field(
default=1, description="The number of repetitions of the " "grover block."
)
helpers
special
¶
versioned_model
¶
VersionedModel (BaseModel)
pydantic-model
¶
Source code in classiq/interface/helpers/versioned_model.py
class VersionedModel(
pydantic.BaseModel, extra=pydantic.Extra.forbid, json_encoders=CUSTOM_ENCODERS
):
version: str = pydantic.Field(default=VERSION)
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
ide
special
¶
ide_data
¶
IDEData (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/ide/ide_data.py
class IDEData(VersionedModel):
qubits: List[IDEDataQubit]
operations: List[IDEDataOperation]
register_data: List[RegisterData]
segment_data: List[InterfaceSegmentData]
circuit_metrics: Optional[CircuitMetrics]
hardware_data: SynthesisHardwareData
creation_time: str
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
jobs
¶
JobDescriptionBase (GenericModel, Generic)
pydantic-model
¶
Source code in classiq/interface/jobs.py
class JobDescriptionBase(GenericModel, Generic[T], json_encoders=CUSTOM_ENCODERS):
kind: str
status: JobStatus
description: Union[T, FailureDetails, Dict]
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
JobDescriptionBase[Any] (JobDescriptionBase)
pydantic-model
¶
Config
¶
getter_dict (Representation)
¶
Hack to make object's smell just enough like dicts for validate_model.
We can't inherit from Mapping[str, Any] because it upsets cython so we have to implement all methods ourselves.
get_field_info(name)
classmethod
¶
Get properties of FieldInfo from the fields
property of the config class.
json_dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False, **kw)
¶
Serialize obj
to a JSON formatted str
.
If skipkeys
is true then dict
keys that are not basic types
(str
, int
, float
, bool
, None
) will be skipped
instead of raising a TypeError
.
If ensure_ascii
is false, then the return value can contain non-ASCII
characters if they appear in strings contained in obj
. Otherwise, all
such characters are escaped in JSON strings.
If check_circular
is false, then the circular reference check
for container types will be skipped and a circular reference will
result in an RecursionError
(or worse).
If allow_nan
is false, then it will be a ValueError
to
serialize out of range float
values (nan
, inf
, -inf
) in
strict compliance of the JSON specification, instead of using the
JavaScript equivalents (NaN
, Infinity
, -Infinity
).
If indent
is a non-negative integer, then JSON array elements and
object members will be pretty-printed with that indent level. An indent
level of 0 will only insert newlines. None
is the most compact
representation.
If specified, separators
should be an (item_separator, key_separator)
tuple. The default is (', ', ': ')
if indent is None
and
(',', ': ')
otherwise. To get the most compact JSON representation,
you should specify (',', ':')
to eliminate whitespace.
default(obj)
is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
If sort_keys is true (default: False
), then the output of
dictionaries will be sorted by key.
To use a custom JSONEncoder
subclass (e.g. one that overrides the
.default()
method to serialize additional types), specify it with
the cls
kwarg; otherwise JSONEncoder
is used.
Source code in classiq/interface/jobs.py
def dumps(obj, *, skipkeys=False, ensure_ascii=True, check_circular=True,
allow_nan=True, cls=None, indent=None, separators=None,
default=None, sort_keys=False, **kw):
"""Serialize ``obj`` to a JSON formatted ``str``.
If ``skipkeys`` is true then ``dict`` keys that are not basic types
(``str``, ``int``, ``float``, ``bool``, ``None``) will be skipped
instead of raising a ``TypeError``.
If ``ensure_ascii`` is false, then the return value can contain non-ASCII
characters if they appear in strings contained in ``obj``. Otherwise, all
such characters are escaped in JSON strings.
If ``check_circular`` is false, then the circular reference check
for container types will be skipped and a circular reference will
result in an ``RecursionError`` (or worse).
If ``allow_nan`` is false, then it will be a ``ValueError`` to
serialize out of range ``float`` values (``nan``, ``inf``, ``-inf``) in
strict compliance of the JSON specification, instead of using the
JavaScript equivalents (``NaN``, ``Infinity``, ``-Infinity``).
If ``indent`` is a non-negative integer, then JSON array elements and
object members will be pretty-printed with that indent level. An indent
level of 0 will only insert newlines. ``None`` is the most compact
representation.
If specified, ``separators`` should be an ``(item_separator, key_separator)``
tuple. The default is ``(', ', ': ')`` if *indent* is ``None`` and
``(',', ': ')`` otherwise. To get the most compact JSON representation,
you should specify ``(',', ':')`` to eliminate whitespace.
``default(obj)`` is a function that should return a serializable version
of obj or raise TypeError. The default simply raises TypeError.
If *sort_keys* is true (default: ``False``), then the output of
dictionaries will be sorted by key.
To use a custom ``JSONEncoder`` subclass (e.g. one that overrides the
``.default()`` method to serialize additional types), specify it with
the ``cls`` kwarg; otherwise ``JSONEncoder`` is used.
"""
# cached encoder
if (not skipkeys and ensure_ascii and
check_circular and allow_nan and
cls is None and indent is None and separators is None and
default is None and not sort_keys and not kw):
return _default_encoder.encode(obj)
if cls is None:
cls = JSONEncoder
return cls(
skipkeys=skipkeys, ensure_ascii=ensure_ascii,
check_circular=check_circular, allow_nan=allow_nan, indent=indent,
separators=separators, default=default, sort_keys=sort_keys,
**kw).encode(obj)
json_loads(s, *, cls=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, object_pairs_hook=None, **kw)
¶
Deserialize s
(a str
, bytes
or bytearray
instance
containing a JSON document) to a Python object.
object_hook
is an optional function that will be called with the
result of any object literal decode (a dict
). The return value of
object_hook
will be used instead of the dict
. This feature
can be used to implement custom decoders (e.g. JSON-RPC class hinting).
object_pairs_hook
is an optional function that will be called with the
result of any object literal decoded with an ordered list of pairs. The
return value of object_pairs_hook
will be used instead of the dict
.
This feature can be used to implement custom decoders. If object_hook
is also defined, the object_pairs_hook
takes priority.
parse_float
, if specified, will be called with the string
of every JSON float to be decoded. By default this is equivalent to
float(num_str). This can be used to use another datatype or parser
for JSON floats (e.g. decimal.Decimal).
parse_int
, if specified, will be called with the string
of every JSON int to be decoded. By default this is equivalent to
int(num_str). This can be used to use another datatype or parser
for JSON integers (e.g. float).
parse_constant
, if specified, will be called with one of the
following strings: -Infinity, Infinity, NaN.
This can be used to raise an exception if invalid JSON numbers
are encountered.
To use a custom JSONDecoder
subclass, specify it with the cls
kwarg; otherwise JSONDecoder
is used.
Source code in classiq/interface/jobs.py
def loads(s, *, cls=None, object_hook=None, parse_float=None,
parse_int=None, parse_constant=None, object_pairs_hook=None, **kw):
"""Deserialize ``s`` (a ``str``, ``bytes`` or ``bytearray`` instance
containing a JSON document) to a Python object.
``object_hook`` is an optional function that will be called with the
result of any object literal decode (a ``dict``). The return value of
``object_hook`` will be used instead of the ``dict``. This feature
can be used to implement custom decoders (e.g. JSON-RPC class hinting).
``object_pairs_hook`` is an optional function that will be called with the
result of any object literal decoded with an ordered list of pairs. The
return value of ``object_pairs_hook`` will be used instead of the ``dict``.
This feature can be used to implement custom decoders. If ``object_hook``
is also defined, the ``object_pairs_hook`` takes priority.
``parse_float``, if specified, will be called with the string
of every JSON float to be decoded. By default this is equivalent to
float(num_str). This can be used to use another datatype or parser
for JSON floats (e.g. decimal.Decimal).
``parse_int``, if specified, will be called with the string
of every JSON int to be decoded. By default this is equivalent to
int(num_str). This can be used to use another datatype or parser
for JSON integers (e.g. float).
``parse_constant``, if specified, will be called with one of the
following strings: -Infinity, Infinity, NaN.
This can be used to raise an exception if invalid JSON numbers
are encountered.
To use a custom ``JSONDecoder`` subclass, specify it with the ``cls``
kwarg; otherwise ``JSONDecoder`` is used.
"""
if isinstance(s, str):
if s.startswith('\ufeff'):
raise JSONDecodeError("Unexpected UTF-8 BOM (decode using utf-8-sig)",
s, 0)
else:
if not isinstance(s, (bytes, bytearray)):
raise TypeError(f'the JSON object must be str, bytes or bytearray, '
f'not {s.__class__.__name__}')
s = s.decode(detect_encoding(s), 'surrogatepass')
if (cls is None and object_hook is None and
parse_int is None and parse_float is None and
parse_constant is None and object_pairs_hook is None and not kw):
return _default_decoder.decode(s)
if cls is None:
cls = JSONDecoder
if object_hook is not None:
kw['object_hook'] = object_hook
if object_pairs_hook is not None:
kw['object_pairs_hook'] = object_pairs_hook
if parse_float is not None:
kw['parse_float'] = parse_float
if parse_int is not None:
kw['parse_int'] = parse_int
if parse_constant is not None:
kw['parse_constant'] = parse_constant
return cls(**kw).decode(s)
prepare_field(field)
classmethod
¶
Optional hook to check or modify fields during model creation.
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
JobDescriptionFailure (JobDescriptionBase[Any])
pydantic-model
¶
Source code in classiq/interface/jobs.py
class JobDescriptionFailure(JobDescriptionBase[Any]):
kind: Literal["failure"] = pydantic.Field(default="failure")
status: FailureStatus
description: FailureDetails
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
JobDescriptionNonFinal (JobDescriptionBase[Any])
pydantic-model
¶
Source code in classiq/interface/jobs.py
class JobDescriptionNonFinal(JobDescriptionBase[Any]):
kind: Literal["non_final"] = pydantic.Field(default="non_final")
status: NonFinalStatus
description: Dict = Field(default_factory=dict)
@pydantic.validator("description")
def validate_empty_description(cls, description: Dict) -> Dict:
if description:
raise ValueError("Non-final job description must be empty")
return description
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
JobDescriptionSuccess (JobDescriptionBase, Generic)
pydantic-model
¶
Source code in classiq/interface/jobs.py
class JobDescriptionSuccess(JobDescriptionBase[T], Generic[T]):
kind: Literal["success"] = pydantic.Field(default="success")
status: SuccessStatus = Field(default=JobStatus.COMPLETED)
description: T
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
JobDescriptionUnion (GenericModel, Generic)
pydantic-model
¶
Source code in classiq/interface/jobs.py
class JobDescriptionUnion(GenericModel, Generic[T], json_encoders=CUSTOM_ENCODERS):
__root__: Union[
JobDescriptionSuccess[T], JobDescriptionFailure, JobDescriptionNonFinal
] = Field(discriminator="kind")
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
model
special
¶
call_synthesis_data
¶
CallSynthesisData (Mapping, Generic)
dataclass
¶
CallSynthesisData(power: Union[classiq.interface.backend.pydantic_backend.ConstrainedStrValue, int] = 1, is_inverse: bool = False, control_states: List[classiq.interface.generator.control_state.ControlState] =
Source code in classiq/interface/model/call_synthesis_data.py
@dataclasses.dataclass
class CallSynthesisData(Mapping):
power: PydanticPowerType = 1
is_inverse: bool = False
control_states: List[ControlState] = dataclasses.field(default_factory=list)
should_control: bool = True
def merge(self, other: "CallSynthesisData") -> "CallSynthesisData":
return CallSynthesisData(
power=_merge_power(self.power, other.power),
is_inverse=self.is_inverse != other.is_inverse,
control_states=self.control_states + other.control_states,
should_control=self.should_control and other.should_control,
)
def set_control(self, ctrl_name: str, ctrl_size: int) -> None:
self.control_states = [
ControlState(
name=ctrl_name,
num_ctrl_qubits=ctrl_size,
)
]
def update_control_state(self, ctrl_size: int, ctrl_state: str) -> None:
prev_ctrl_state = self.control_states.pop()
self.control_states.append(
ControlState(
name=prev_ctrl_state.name,
num_ctrl_qubits=ctrl_size,
ctrl_state=ctrl_state,
)
)
@property
def has_control(self) -> bool:
return bool(self.control_states)
def __getitem__(self, key: str) -> Any:
return dataclasses.asdict(self)[key]
def __iter__(self) -> Iterator[str]:
return iter(dataclasses.asdict(self))
def __len__(self) -> int:
return len(dataclasses.asdict(self))
handle_binding
¶
HandleBinding (ASTNode)
pydantic-model
¶
Source code in classiq/interface/model/handle_binding.py
class HandleBinding(ASTNode):
name: str
class Config:
frozen = True
extra = Extra.forbid
def __str__(self) -> str:
return self.name
def is_bindable(self) -> bool:
return True
__str__(self)
special
¶
Return str(self).
Source code in classiq/interface/model/handle_binding.py
def __str__(self) -> str:
return self.name
model
¶
Model (VersionedModel, ASTNode)
pydantic-model
¶
All the relevant data for generating quantum circuit in one place.
Source code in classiq/interface/model/model.py
class Model(VersionedModel, ASTNode):
"""
All the relevant data for generating quantum circuit in one place.
"""
kind: Literal["user"] = pydantic.Field(default=USER_MODEL_MARKER)
# Must be validated before logic_flow
functions: List[NativeFunctionDefinition] = pydantic.Field(
default_factory=list,
description="The user-defined custom type library.",
)
types: List[StructDeclaration] = pydantic.Field(
default_factory=list,
description="The user-defined custom function library.",
)
classical_execution_code: str = pydantic.Field(
description="The classical execution code of the model", default=""
)
constants: List[Constant] = pydantic.Field(
default_factory=list,
)
constraints: Constraints = pydantic.Field(default_factory=Constraints)
execution_preferences: ExecutionPreferences = pydantic.Field(
default_factory=ExecutionPreferences
)
preferences: Preferences = pydantic.Field(default_factory=Preferences)
@property
def main_func(self) -> NativeFunctionDefinition:
return self.function_dict[MAIN_FUNCTION_NAME] # type:ignore[return-value]
@property
def body(self) -> StatementBlock:
return self.main_func.body
@pydantic.validator("preferences", always=True)
def _seed_suffix_randomizer(cls, preferences: Preferences) -> Preferences:
SUFFIX_RANDOMIZER.seed(preferences.random_seed)
return preferences
def _get_qualified_direction(
self, port_name: str, direction: PortDeclarationDirection
) -> PortDeclarationDirection:
if port_name in self.main_func.port_declarations:
return PortDeclarationDirection.Inout
return direction
@property
def function_dict(self) -> Dict[str, QuantumFunctionDeclaration]:
return nameables_to_dict(self.functions)
@pydantic.validator("functions", always=True)
def _add_empty_main(
cls, functions: List[NativeFunctionDefinition]
) -> List[NativeFunctionDefinition]:
function_dict = nameables_to_dict(functions)
if MAIN_FUNCTION_NAME not in function_dict:
functions.append(_create_empty_main_function())
return functions
@pydantic.root_validator()
def validate_static_correctness(cls, values: Dict[str, Any]) -> Dict[str, Any]:
functions: Optional[List[QuantumFunctionDeclaration]] = values.get("functions")
if functions is None:
return values
resolve_function_calls(
values,
nameables_to_dict(functions),
)
for func_def in functions:
if isinstance(func_def, NativeFunctionDefinition):
func_def.validate_body()
return values
@pydantic.validator("types")
def types_validator(cls, types: List[StructDeclaration]) -> List[StructDeclaration]:
user_defined_types: Set[str] = set()
for ctype in types:
if ctype.name in StructDeclaration.BUILTIN_STRUCT_DECLARATIONS:
raise ClassiqValueError(
TYPE_NAME_CONFLICT_BUILTIN.format(name=ctype.name)
)
if ctype.name in user_defined_types:
raise ClassiqValueError(TYPE_NAME_CONFLICT_USER.format(name=ctype.name))
user_defined_types.add(ctype.name)
return types
def get_model(self) -> SerializedModel:
return SerializedModel(
self.json(exclude_defaults=True, exclude_unset=True, indent=2)
)
@pydantic.validator("functions")
def _validate_entry_point(
cls, functions: List[NativeFunctionDefinition]
) -> List[NativeFunctionDefinition]:
function_dict = nameables_to_dict(functions)
if MAIN_FUNCTION_NAME not in function_dict:
raise ClassiqValueError("The model must contain a `main` function")
if any(
pd.direction != PortDeclarationDirection.Output
for pd in function_dict[MAIN_FUNCTION_NAME].port_declarations.values()
):
raise ClassiqValueError("Function 'main' cannot declare quantum inputs")
return functions
@pydantic.validator("constants")
def _validate_constants(cls, constants: List[Constant]) -> List[Constant]:
constant_definition_counts = Counter(
[constant.name for constant in constants]
).items()
multiply_defined_constants = {
constant for constant, count in constant_definition_counts if count > 1
}
if len(multiply_defined_constants) > 0:
raise ClassiqValueError(
f"The following constants were defined more than once: "
f"{multiply_defined_constants}"
)
return constants
classical_execution_code: str
pydantic-field
¶
The classical execution code of the model
functions: List[classiq.interface.model.native_function_definition.NativeFunctionDefinition]
pydantic-field
¶
The user-defined custom type library.
types: List[classiq.interface.generator.types.struct_declaration.StructDeclaration]
pydantic-field
¶
The user-defined custom function library.
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
VersionedSerializedModel (VersionedModel)
pydantic-model
¶
Source code in classiq/interface/model/model.py
class VersionedSerializedModel(VersionedModel):
model: SerializedModel
__json_encoder__(obj)
special
staticmethod
¶
partial(func, args, *keywords) - new function with partial application of the given arguments and keywords.
native_function_definition
¶
NativeFunctionDefinition (QuantumFunctionDeclaration)
pydantic-model
¶
Facilitates the creation of a user-defined composite function
This class sets extra to forbid so that it can be used in a Union and not "steal" objects from other classes.
Source code in classiq/interface/model/native_function_definition.py
class NativeFunctionDefinition(QuantumFunctionDeclaration):
"""
Facilitates the creation of a user-defined composite function
This class sets extra to forbid so that it can be used in a Union and not "steal"
objects from other classes.
"""
body: StatementBlock = pydantic.Field(
default_factory=list, description="List of function calls to perform."
)
def validate_body(self) -> None:
handle_validator = HandleValidator(self.port_declarations)
for statement in self.body:
if isinstance(statement, VariableDeclarationStatement):
handle_validator.handle_variable_declaration(statement)
else:
handle_validator.handle_operation(statement)
handle_validator.report_errored_handles(ClassiqValueError)
body: List[Union[classiq.interface.model.quantum_function_call.QuantumFunctionCall, classiq.interface.model.quantum_expressions.arithmetic_operation.ArithmeticOperation, classiq.interface.model.quantum_expressions.amplitude_loading_operation.AmplitudeLoadingOperation, classiq.interface.model.variable_declaration_statement.VariableDeclarationStatement, classiq.interface.model.bind_operation.BindOperation, classiq.interface.model.inplace_binary_operation.InplaceBinaryOperation, classiq.interface.model.repeat.Repeat, classiq.interface.model.power.Power, classiq.interface.model.invert.Invert, classiq.interface.model.classical_if.ClassicalIf, classiq.interface.model.control.Control, classiq.interface.model.within_apply_operation.WithinApply]]
pydantic-field
¶
List of function calls to perform.
quantum_expressions
special
¶
arithmetic_operation
¶
ArithmeticOperation (QuantumAssignmentOperation)
pydantic-model
¶
Source code in classiq/interface/model/quantum_expressions/arithmetic_operation.py
class ArithmeticOperation(QuantumAssignmentOperation):
inplace_result: bool = pydantic.Field(
description="Determines whether the result variable is initialized",
)
def initialize_var_types(
self,
var_types: Dict[str, QuantumType],
machine_precision: int,
) -> None:
super().initialize_var_types(var_types, machine_precision)
self._result_type = compute_arithmetic_result_type(
self.expression.expr, var_types, machine_precision
)
@property
def wiring_inouts(
self,
) -> Mapping[
str, Union[SlicedHandleBinding, SubscriptHandleBinding, HandleBinding]
]:
inouts = dict(super().wiring_inouts)
if self.inplace_result:
inouts[self.result_name()] = self.result_var
return inouts
@property
def wiring_outputs(self) -> Mapping[str, HandleBinding]:
if self.inplace_result:
return {}
return super().wiring_outputs
@classmethod
def result_name(cls) -> str:
return ARITHMETIC_EXPRESSION_RESULT_NAME
inplace_result: bool
pydantic-field
required
¶Determines whether the result variable is initialized
quantum_expression
¶
QuantumAssignmentOperation (QuantumExpressionOperation)
pydantic-model
¶
Source code in classiq/interface/model/quantum_expressions/quantum_expression.py
class QuantumAssignmentOperation(QuantumExpressionOperation):
result_var: HandleBinding = pydantic.Field(
description="The variable storing the expression result"
)
_result_type: Optional[QuantumType] = pydantic.PrivateAttr(
default=None,
)
@property
def result_type(self) -> QuantumType:
assert self._result_type is not None
return self._result_type
@property
def wiring_outputs(self) -> Mapping[str, HandleBinding]:
return {self.result_name(): self.result_var}
@classmethod
@abc.abstractmethod
def result_name(cls) -> str:
raise NotImplementedError()
result_var: HandleBinding
pydantic-field
required
¶The variable storing the expression result
VarRefCollector (NodeVisitor)
¶
Source code in classiq/interface/model/quantum_expressions/quantum_expression.py
class VarRefCollector(ast.NodeVisitor):
def __init__(self) -> None:
self.var_names: Set[str] = set()
def generic_visit(self, node: ast.AST) -> None:
if isinstance(node, ast.Name) and node.id not in set(
SYMPY_SUPPORTED_EXPRESSIONS
) | set(DEFAULT_SUPPORTED_FUNC_NAMES):
self.var_names.add(node.id)
super().generic_visit(node)
generic_visit(self, node)
¶Called if no explicit visitor function exists for a node.
Source code in classiq/interface/model/quantum_expressions/quantum_expression.py
def generic_visit(self, node: ast.AST) -> None:
if isinstance(node, ast.Name) and node.id not in set(
SYMPY_SUPPORTED_EXPRESSIONS
) | set(DEFAULT_SUPPORTED_FUNC_NAMES):
self.var_names.add(node.id)
super().generic_visit(node)
quantum_function_call
¶
QuantumFunctionCall (QuantumOperation)
pydantic-model
¶
Source code in classiq/interface/model/quantum_function_call.py
class QuantumFunctionCall(QuantumOperation):
function: Union[str, OperandIdentifier] = pydantic.Field(
description="The function that is called"
)
params: Dict[str, Expression] = pydantic.Field(default_factory=dict)
inputs: Dict[str, HandleBinding] = pydantic.Field(
default_factory=dict,
description="A mapping from the input name to the wire it connects to",
)
inouts: Dict[
str, Union[SlicedHandleBinding, SubscriptHandleBinding, HandleBinding]
] = pydantic.Field(
default_factory=dict,
description="A mapping from in/out name to the wires that connect to it",
)
outputs: Dict[str, HandleBinding] = pydantic.Field(
default_factory=dict,
description="A mapping from the output name to the wire it connects to",
)
operands: Dict[str, QuantumOperand] = pydantic.Field(
description="Function calls passed to the operator",
default_factory=dict,
)
positional_args: List[ArgValue] = pydantic.Field(default_factory=list)
_func_decl: Optional[QuantumFunctionDeclaration] = pydantic.PrivateAttr(
default=None
)
_synthesis_data: CallSynthesisData = pydantic.PrivateAttr(
default_factory=CallSynthesisData
)
@property
def func_decl(self) -> QuantumFunctionDeclaration:
if self._func_decl is None:
raise ClassiqError("Accessing an unresolved quantum function call")
return self._func_decl
def set_func_decl(self, fd: Optional[FunctionDeclaration]) -> None:
if fd is not None and not isinstance(fd, QuantumFunctionDeclaration):
raise ClassiqValueError(
"the declaration of a quantum function call cannot be set to a non-quantum function declaration."
)
self._func_decl = fd
@property
def func_name(self) -> str:
if isinstance(self.function, OperandIdentifier):
return self.function.name
return self.function
@property
def wiring_inputs(self) -> Mapping[str, HandleBinding]:
return self.inputs
@property
def wiring_inouts(
self,
) -> Mapping[
str, Union[SlicedHandleBinding, SubscriptHandleBinding, HandleBinding]
]:
return self.inouts
@property
def wiring_outputs(self) -> Mapping[str, HandleBinding]:
return self.outputs
def get_positional_args(self) -> List[ArgValue]:
result: List[ArgValue] = self.positional_args
if not result:
result = list(self.params.values())
result.extend(self.operands.values())
result.extend(self.inputs.values())
result.extend(self.inouts.values())
result.extend(self.outputs.values())
return result
@property
def pos_param_args(self) -> Dict[str, Expression]:
return dict(
zip(
self.func_decl.param_decls.keys(),
(
param
for param in self.positional_args
if isinstance(param, Expression)
),
)
)
@property
def pos_operand_args(self) -> Dict[str, "QuantumOperand"]:
return dict(
zip(
self.func_decl.operand_declarations.keys(),
(
param
for param in self.positional_args
if not isinstance(param, (Expression, HandleBinding))
),
)
)
@property
def pos_port_args(self) -> Dict[str, HandleBinding]:
return dict(
zip(
self.func_decl.port_declarations.keys(),
(
param
for param in self.positional_args
if isinstance(param, HandleBinding)
),
)
)
def _update_pos_port_params(self) -> None:
for name, port_decl in self.func_decl.port_declarations.items():
if port_decl.direction == PortDeclarationDirection.Input:
self.inputs[name] = self.pos_port_args[name]
elif port_decl.direction == PortDeclarationDirection.Output:
self.outputs[name] = self.pos_port_args[name]
else:
self.inouts[name] = self.pos_port_args[name]
def _reduce_positional_args_to_keywords(self) -> None:
self.params.update(self.pos_param_args)
self.operands.update(self.pos_operand_args)
self._update_pos_port_params()
def resolve_function_decl(
self,
function_dict: Mapping[str, QuantumFunctionDeclaration],
check_operands: bool,
) -> None:
if self._func_decl is None:
func_decl = function_dict.get(self.func_name)
if func_decl is None:
raise ClassiqValueError(
f"Error resolving function {self.func_name}, the function is not found in included library."
)
self.set_func_decl(func_decl)
if self.positional_args:
self._reduce_positional_args_to_keywords()
_check_params_against_declaration(
set(self.params.keys()),
set(self.func_decl.param_decls.keys()),
self.func_decl.name,
)
_check_ports_against_declaration(self, self.func_decl)
_check_params_against_declaration(
set(self.operands.keys()),
set(self.func_decl.operand_declarations.keys()),
self.func_name,
)
if check_operands:
_check_operands_against_declaration(self, self.func_decl, function_dict)
for name, op in self.operands.items():
op_decl = self.func_decl.operand_declarations[name]
for qlambda in get_lambda_defs(op):
if isinstance(qlambda, QuantumLambdaFunction):
qlambda.set_op_decl(op_decl)
@property
def synthesis_data(self) -> CallSynthesisData:
return self._synthesis_data
def merge_synthesis_data(self, other: CallSynthesisData) -> None:
self._synthesis_data = self._synthesis_data.merge(other)
@pydantic.root_validator()
def validate_handles(cls, values: Dict[str, Any]) -> Dict[str, Any]:
inputs = values.get("inputs", dict())
outputs = values.get("outputs", dict())
inouts = values.get("inouts", dict())
_validate_no_duplicated_ports(inputs, outputs, inouts)
_validate_no_duplicated_handles(inputs, outputs, inouts)
_validate_no_mixing_sliced_and_whole_handles(inouts)
return values
function: Union[str, classiq.interface.model.quantum_function_call.OperandIdentifier]
pydantic-field
required
¶
The function that is called
inouts: Dict[str, Union[classiq.interface.model.handle_binding.SlicedHandleBinding, classiq.interface.model.handle_binding.SubscriptHandleBinding, classiq.interface.model.handle_binding.HandleBinding]]
pydantic-field
¶
A mapping from in/out name to the wires that connect to it
inputs: Dict[str, classiq.interface.model.handle_binding.HandleBinding]
pydantic-field
¶
A mapping from the input name to the wire it connects to
operands: Dict[str, Union[str, classiq.interface.model.quantum_lambda_function.QuantumLambdaFunction, List[Union[str, classiq.interface.model.quantum_lambda_function.QuantumLambdaFunction]], classiq.interface.model.quantum_lambda_function.LambdaListComprehension]]
pydantic-field
¶
Function calls passed to the operator
outputs: Dict[str, classiq.interface.model.handle_binding.HandleBinding]
pydantic-field
¶
A mapping from the output name to the wire it connects to
quantum_function_declaration
¶
QuantumFunctionDeclaration (FunctionDeclaration)
pydantic-model
¶
Facilitates the creation of a common quantum function interface object.
Source code in classiq/interface/model/quantum_function_declaration.py
class QuantumFunctionDeclaration(FunctionDeclaration):
"""
Facilitates the creation of a common quantum function interface object.
"""
port_declarations: Dict[str, PortDeclaration] = pydantic.Field(
description="The input and output ports of the function.",
default_factory=dict,
)
operand_declarations: Mapping[str, "QuantumOperandDeclaration"] = pydantic.Field(
description="The expected interface of the quantum function operands",
default_factory=dict,
)
positional_arg_declarations: List[PositionalArg] = pydantic.Field(
default_factory=list
)
BUILTIN_FUNCTION_DECLARATIONS: ClassVar[Dict[str, "QuantumFunctionDeclaration"]] = (
{}
)
@property
def input_set(self) -> Set[str]:
return set(self.inputs.keys())
@property
def output_set(self) -> Set[str]:
return set(self.outputs.keys())
@property
def inputs(self) -> ArithmeticIODict:
return _ports_to_registers(self.port_declarations, PortDirection.Input)
@property
def outputs(self) -> ArithmeticIODict:
return _ports_to_registers(self.port_declarations, PortDirection.Output)
def update_logic_flow(
self, function_dict: Mapping[str, "QuantumFunctionDeclaration"]
) -> None:
pass
@property
def port_names(self) -> List[str]:
return list(self.port_declarations.keys())
@property
def operand_names(self) -> List[str]:
return list(self.operand_declarations.keys())
def ports_by_direction(
self, direction: PortDirection
) -> Mapping[str, PortDeclaration]:
return {
name: port
for name, port in self.port_declarations.items()
if port.direction.includes_port_direction(direction)
}
def ports_by_declaration_direction(
self, direction: PortDeclarationDirection
) -> Set[str]:
return {
name
for name, port in self.port_declarations.items()
if port.direction == direction
}
def get_positional_arg_decls(self) -> List[PositionalArg]:
result: List[PositionalArg] = self.positional_arg_declarations
if not result:
result = [
ClassicalParameterDeclaration(name=name, classical_type=ctype)
for name, ctype in self.param_decls.items()
]
result.extend(self.operand_declarations.values())
result.extend(self.port_declarations.values())
return result
@pydantic.validator("operand_declarations")
def _validate_operand_declarations_names(
cls, operand_declarations: Dict[str, "QuantumOperandDeclaration"]
) -> Dict[str, "QuantumOperandDeclaration"]:
validate_nameables_mapping(operand_declarations, "Operand")
return operand_declarations
@pydantic.validator("port_declarations")
def _validate_port_declarations_names(
cls, port_declarations: Dict[str, PortDeclaration]
) -> Dict[str, PortDeclaration]:
validate_nameables_mapping(port_declarations, "Port")
return port_declarations
@pydantic.root_validator()
def _validate_params_and_operands_uniqueness(
cls, values: Dict[str, Any]
) -> Dict[str, Any]:
operand_declarations = values.get("operand_declarations")
parameter_declarations = values.get("param_decls")
port_declarations = values.get("port_declarations")
operand_parameter = validate_nameables_no_overlap(
operand_declarations, parameter_declarations, "operand", "parameter"
)
operand_port = validate_nameables_no_overlap(
operand_declarations, port_declarations, "operand", "port"
)
parameter_port = validate_nameables_no_overlap(
parameter_declarations, port_declarations, "parameter", "port"
)
error_message = ",".join(
msg
for msg in [operand_parameter, operand_port, parameter_port]
if msg is not None
)
if error_message:
raise ClassiqValueError(error_message)
return values
@pydantic.root_validator()
def _reduce_positional_declarations_to_keyword(
cls, values: Dict[str, Any]
) -> Dict[str, Any]:
operand_declarations = values.get("operand_declarations", dict())
parameter_declarations = values.get("param_decls", dict())
port_declarations = values.get("port_declarations", dict())
positional_arg_declarations = values.get("positional_arg_declarations", list())
_populate_declaration_dicts_with_positional_lists(
positional_arg_declarations,
parameter_declarations,
ClassicalParameterDeclaration,
)
_populate_declaration_dicts_with_positional_lists(
positional_arg_declarations,
operand_declarations,
QuantumOperandDeclaration,
)
_populate_declaration_dicts_with_positional_lists(
positional_arg_declarations, port_declarations, PortDeclaration
)
values["operand_declarations"] = operand_declarations
values["param_decls"] = parameter_declarations
values["port_declarations"] = port_declarations
return values
QuantumOperandDeclaration (QuantumFunctionDeclaration)
pydantic-model
¶
Source code in classiq/interface/model/quantum_function_declaration.py
class QuantumOperandDeclaration(QuantumFunctionDeclaration):
is_list: bool = pydantic.Field(
description="Indicate whether the operand expects an unnamed list of lambdas",
default=False,
)
is_list: bool
pydantic-field
¶
Indicate whether the operand expects an unnamed list of lambdas
quantum_lambda_function
¶
LambdaListComprehension (ASTNode)
pydantic-model
¶
Specification of a list of lambda functions iteratively
Source code in classiq/interface/model/quantum_lambda_function.py
class LambdaListComprehension(ASTNode):
"""
Specification of a list of lambda functions iteratively
"""
count: Expression = pydantic.Field(
description="The number of lambda functions in the list"
)
index_var: str = pydantic.Field(
description="The name of the integer variable holding the iteration index"
)
func: QuantumLambdaFunction = pydantic.Field(
description="A lambda function definition replicated for index values 0 to count-1"
)
count: Expression
pydantic-field
required
¶
The number of lambda functions in the list
func: QuantumLambdaFunction
pydantic-field
required
¶
A lambda function definition replicated for index values 0 to count-1
index_var: str
pydantic-field
required
¶
The name of the integer variable holding the iteration index
QuantumLambdaFunction (ASTNode)
pydantic-model
¶
The definition of an anonymous function passed as operand to higher-level functions
Source code in classiq/interface/model/quantum_lambda_function.py
class QuantumLambdaFunction(ASTNode):
"""
The definition of an anonymous function passed as operand to higher-level functions
"""
rename_params: Dict[str, str] = pydantic.Field(
default_factory=dict,
description="Mapping of the declared param to the actual variable name used ",
)
body: "StatementBlock" = pydantic.Field(
description="A list of function calls passed to the operator"
)
_func_decl: Optional[QuantumOperandDeclaration] = pydantic.PrivateAttr(default=None)
@property
def func_decl(self) -> Optional[QuantumOperandDeclaration]:
return self._func_decl
def set_op_decl(self, fd: QuantumOperandDeclaration) -> None:
self._func_decl = fd
body: List[Union[classiq.interface.model.quantum_function_call.QuantumFunctionCall, classiq.interface.model.quantum_expressions.arithmetic_operation.ArithmeticOperation, classiq.interface.model.quantum_expressions.amplitude_loading_operation.AmplitudeLoadingOperation, classiq.interface.model.variable_declaration_statement.VariableDeclarationStatement, classiq.interface.model.bind_operation.BindOperation, classiq.interface.model.inplace_binary_operation.InplaceBinaryOperation, classiq.interface.model.repeat.Repeat, classiq.interface.model.power.Power, classiq.interface.model.invert.Invert, classiq.interface.model.classical_if.ClassicalIf, classiq.interface.model.control.Control, classiq.interface.model.within_apply_operation.WithinApply]]
pydantic-field
required
¶
A list of function calls passed to the operator
rename_params: Dict[str, str]
pydantic-field
¶
Mapping of the declared param to the actual variable name used
quantum_register
¶
QReg
¶
Represents a logical sequence of qubits.
The QReg can be used as an in_wires
or out_wires
argument to Model function calls,
assisting in model connectivity.
Source code in classiq/interface/model/quantum_register.py
class QReg:
"""Represents a logical sequence of qubits.
The QReg can be used as an `in_wires` or `out_wires` argument to Model function calls,
assisting in model connectivity.
"""
def __init__(self, size: int) -> None:
"""Initializes a new QReg with the specified number of qubits.
Args:
size (int): The number of qubits in the QReg.
"""
if size <= 0:
raise ClassiqQRegError(f"Cannot create {size} new qubits")
self._qubits = [Qubit() for _ in range(size)]
@classmethod
def _from_qubits(cls, qubits: List[Qubit]) -> "QReg":
if (
not isinstance(qubits, list)
or not all(isinstance(qubit, Qubit) for qubit in qubits)
or len(qubits) == 0
):
raise ClassiqQRegError(f"Cannot create QReg from {qubits}")
qreg = cls(size=1)
qreg._qubits = qubits
return qreg
def __getitem__(self, key: Union[int, slice]) -> "QReg":
state = self._qubits[key]
return QReg._from_qubits(state if isinstance(state, list) else [state])
def __setitem__(self, key: Union[int, slice], value: "QReg") -> None:
if isinstance(key, int) and len(value) != 1:
raise ClassiqQRegError(
f"Size mismatch: value size {len(value)}, expected size 1"
)
self._qubits[key] = value._qubits[0] if isinstance(key, int) else value._qubits # type: ignore[call-overload]
def __iter__(self) -> Iterator["QReg"]:
return iter([self[idx] for idx in range(len(self))])
def __eq__(self, other: Any) -> bool:
return isinstance(other, QReg) and self._qubits == other._qubits
def isoverlapping(self, other: "QReg") -> bool:
return isinstance(other, QReg) and not set(self._qubits).isdisjoint(
set(other._qubits)
)
@classmethod
def concat(cls, *qregs: "QReg") -> "QReg":
"""Concatenate two QRegs.
Args:
qregs: the QRegs to concat in order, as separate arguments.
Returns:
A QReg representing the concatenation of the given QRegs.
"""
qubits = list(itertools.chain.from_iterable(qreg._qubits for qreg in qregs))
return cls._from_qubits(qubits)
def __len__(self) -> int:
return len(self._qubits)
@property
def qubits(self) -> List[Qubit]:
return self._qubits
def __class_getitem__(cls, params: Any) -> QRegGenericAlias:
# Supporting python 3.7+, thus returning `typing._GenericAlias` instead of `types.GenericAlias`
if isinstance(params, int):
return QRegGenericAlias(cls, params)
raise ClassiqQRegError(f"Invalid size: {params} ; int required")
def to_register_user_input(self, name: str = "") -> RegisterUserInput:
fraction_places = getattr(self, "fraction_places", 0)
is_signed = getattr(self, "is_signed", False)
return RegisterUserInput(
name=name,
size=len(self),
is_signed=is_signed,
fraction_places=fraction_places,
)
@staticmethod
def from_arithmetic_info(info: RegisterArithmeticInfo) -> "QReg":
method = _get_qreg_type_from_arithmetic_info(info)
frac_attr = {"fraction_places": info.fraction_places} if info.is_frac else {}
return method(size=info.size, **frac_attr)
__init__(self, size)
special
¶
Initializes a new QReg with the specified number of qubits.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
size |
int |
The number of qubits in the QReg. |
required |
Source code in classiq/interface/model/quantum_register.py
def __init__(self, size: int) -> None:
"""Initializes a new QReg with the specified number of qubits.
Args:
size (int): The number of qubits in the QReg.
"""
if size <= 0:
raise ClassiqQRegError(f"Cannot create {size} new qubits")
self._qubits = [Qubit() for _ in range(size)]
concat(*qregs)
classmethod
¶
Concatenate two QRegs.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
qregs |
QReg |
the QRegs to concat in order, as separate arguments. |
() |
Returns:
Type | Description |
---|---|
QReg |
A QReg representing the concatenation of the given QRegs. |
Source code in classiq/interface/model/quantum_register.py
@classmethod
def concat(cls, *qregs: "QReg") -> "QReg":
"""Concatenate two QRegs.
Args:
qregs: the QRegs to concat in order, as separate arguments.
Returns:
A QReg representing the concatenation of the given QRegs.
"""
qubits = list(itertools.chain.from_iterable(qreg._qubits for qreg in qregs))
return cls._from_qubits(qubits)
validation_handle
¶
ValidationHandle
dataclass
¶
ValidationHandle(initial_state: Optional[classiq.interface.model.validation_handle.HandleState] = None, errors: Optional[List[str]] = None)
Source code in classiq/interface/model/validation_handle.py
@dataclasses.dataclass
class ValidationHandle:
_state: HandleState
errors: List[str] = dataclasses.field(default_factory=list)
def __init__(
self,
initial_state: Optional[HandleState] = None,
errors: Optional[List[str]] = None,
) -> None:
if initial_state is None and not errors:
raise ClassiqError("Missing initial state for ValidationHandle")
self._state = initial_state or HandleState.ERRORED
self.errors = errors or []
@property
def state(self) -> HandleState:
return self._state
def append_error(self, error: str) -> None:
self.errors.append(error)
self._state = HandleState.ERRORED
def initialize(self) -> None:
self._state = HandleState.INITIALIZED
def uninitialize(self) -> None:
self._state = HandleState.UNINITIALIZED
qmod
special
¶
cfunc
¶
get_caller_locals()
¶
Print the local variables in the caller's frame.
Source code in classiq/qmod/cfunc.py
def get_caller_locals() -> Dict[str, Any]:
"""Print the local variables in the caller's frame."""
import inspect
frame = inspect.currentframe()
try:
assert frame is not None
cfunc_frame = frame.f_back
assert cfunc_frame is not None
caller_frame = cfunc_frame.f_back
assert caller_frame is not None
return caller_frame.f_locals
finally:
# See here for information about the `del`
# https://docs.python.org/3/library/inspect.html#the-interpreter-stack
del frame
native
special
¶
expression_to_qmod
¶
ASTToQMODCode
dataclass
¶
ASTToQMODCode(level: int, decimal_precision: int, indent_seq: str = ' ')
Source code in classiq/qmod/native/expression_to_qmod.py
@dataclass
class ASTToQMODCode:
level: int
decimal_precision: int
indent_seq: str = " "
@property
def indent(self) -> str:
return self.level * self.indent_seq
def visit(self, node: ast.AST) -> str:
return self.ast_to_code(node)
def ast_to_code(self, node: ast.AST) -> str:
if isinstance(node, ast.Module):
return self.indent.join(self.ast_to_code(child) for child in node.body)
elif isinstance(node, ast.Attribute):
# Enum attribute access
if not isinstance(node.attr, str):
raise AssertionError("Error parsing enum attribute access")
access_operator = "." if node.attr in CLASSICAL_ATTRIBUTES else "::"
return f"{self.ast_to_code(node.value)!s}{access_operator}{node.attr!s}"
elif isinstance(node, ast.Name):
return node.id
elif isinstance(node, ast.Num):
return str(np.round(node.n, self.decimal_precision))
elif isinstance(node, ast.Str):
return repr(node.s)
elif isinstance(node, ast.Constant):
return repr(node.value)
elif isinstance(node, ast.BinOp):
return "({} {} {})".format(
self.ast_to_code(node.left),
BINARY_OPS[type(node.op)],
self.ast_to_code(node.right),
)
elif isinstance(node, ast.UnaryOp):
unary_op = UNARY_OPS[type(node.op)]
space = " " if unary_op == "not" else ""
return f"({unary_op}{space}{self.ast_to_code(node.operand)})"
elif isinstance(node, ast.BoolOp):
return "({})".format(
(" " + BOOL_OPS[type(node.op)] + " ").join(
self.ast_to_code(value) for value in node.values
)
)
elif isinstance(node, ast.Compare):
if len(node.ops) != 1 or len(node.comparators) != 1:
raise AssertionError("Error parsing comparison expression.")
return "({} {} {})".format(
self.ast_to_code(node.left),
COMPARE_OPS[type(node.ops[0])],
self.ast_to_code(node.comparators[0]),
)
elif isinstance(node, ast.List):
elts = node.elts
elements = self.indent_items(
lambda: [self.ast_to_code(element) for element in elts]
)
return f"[{elements}]"
elif isinstance(node, ast.Subscript):
return f"{self.ast_to_code(node.value)}[{_remove_redundant_parentheses(self.ast_to_code(node.slice))}]"
elif isinstance(node, ast.Slice):
# A QMOD expression does not support slice step
if node.lower is None or node.upper is None or node.step is not None:
raise AssertionError("Error parsing slice expression.")
return f"{self.ast_to_code(node.lower)}:{self.ast_to_code(node.upper)}"
elif isinstance(node, ast.Call):
func = self.ast_to_code(node.func)
if func == "get_field":
if len(node.args) != 2:
raise AssertionError("Error parsing struct field access.")
field = str(self.ast_to_code(node.args[1])).replace("'", "")
if not IDENTIFIER.match(field):
raise AssertionError("Error parsing struct field access.")
return f"{self.ast_to_code(node.args[0])}.{field}"
elif func == "struct_literal":
if len(node.args) != 1 or not isinstance(node.args[0], ast.Name):
raise AssertionError("Error parsing struct literal.")
keywords = node.keywords
initializer_list = self.indent_items(
lambda: [
f"{keyword.arg} = {self._cleaned_ast_to_code(keyword.value)}"
for keyword in keywords
if keyword.arg is not None
]
)
return f"{self.ast_to_code(node.args[0])} {{{initializer_list}}}"
else:
return "{}({})".format(
func, ", ".join(self._cleaned_ast_to_code(arg) for arg in node.args)
)
elif isinstance(node, ast.Expr):
return self._cleaned_ast_to_code(node.value)
else:
raise AssertionError("Error parsing expression: unsupported AST node.")
def indent_items(self, items: Callable[[], List[str]]) -> str:
should_indent = (
len("".join([i.strip() for i in items()])) >= LIST_FORMAT_CHAR_LIMIT
)
if should_indent:
self.level += 1
left_ws = "\n" + self.indent
inner_ws = ",\n" + self.indent
else:
left_ws = ""
inner_ws = ", "
items_ = items()
if should_indent:
self.level -= 1
right_ws = "\n" + self.indent
else:
right_ws = ""
return f"{left_ws}{inner_ws.join(items_)}{right_ws}"
def _cleaned_ast_to_code(self, node: ast.AST) -> str:
return _remove_redundant_parentheses(self.ast_to_code(node))