Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 83 additions & 5 deletions crates/binding-python/cqlib/circuit/operation.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ or classical control). :class:`ValueOperation` pairs an instruction with
qubits and parameters to form a complete circuit operation.
"""

from typing import Any
import numpy as np
from numpy.typing import NDArray
from .bit import Qubit
Expand Down Expand Up @@ -112,6 +111,37 @@ class ValueInstruction:
@property
def is_instruction(self) -> bool: ...
@property
def name(self) -> str:
"""Human-readable name (e.g. ``"h"``, ``"cx"``, ``"measure"``)."""
...
@property
def instruction_type(self) -> str:
"""One of ``"standard"``, ``"mcgate"``, ``"unitary"``, ``"circuit"``,
``"directive"``, ``"classical_data"``, ``"classical_control"``, ``"delay"``."""
...
@property
def is_standard(self) -> bool: ...
@property
def is_mcgate(self) -> bool: ...
@property
def is_unitary(self) -> bool: ...
@property
def is_circuit_gate(self) -> bool: ...
@property
def is_directive(self) -> bool: ...
@property
def is_classical_data(self) -> bool: ...
@property
def is_delay(self) -> bool: ...
@property
def standard_gate(self) -> StandardGate | None:
"""The :class:`StandardGate` if this is a standard-gate instruction."""
...
@property
def directive(self) -> Directive | None:
"""The :class:`Directive` if this is a directive instruction."""
...
@property
def instruction(self) -> Instruction | None:
"""The inner :class:`Instruction` when this is a plain instruction."""
...
Expand All @@ -130,17 +160,32 @@ class ValueOperation:
This is the public construction boundary. Use the static factories to
create operations from gates with their bound parameters preserved.
"""
def __init__(self, instruction: ValueInstruction, qubits: list[Qubit], params: list[float | Parameter] | None = ..., label: str | None = ...) -> None: ...
def __init__(
self,
instruction: ValueInstruction,
qubits: list[Qubit],
params: list[float | Parameter] | None = ...,
label: str | None = ...,
) -> None: ...
@staticmethod
def from_instruction(instruction: Instruction, qubits: list[Qubit], params: list[float | Parameter] | None = ..., label: str | None = ...) -> ValueOperation:
def from_instruction(
instruction: Instruction,
qubits: list[Qubit],
params: list[float | Parameter] | None = ...,
label: str | None = ...,
) -> ValueOperation:
"""Create from a storage :class:`Instruction` with explicit parameters."""
...
@staticmethod
def from_standard_gate(gate: StandardGate, qubits: list[Qubit], label: str | None = ...) -> ValueOperation:
def from_standard_gate(
gate: StandardGate, qubits: list[Qubit], label: str | None = ...
) -> ValueOperation:
"""Create while preserving parameters bound to a :class:`StandardGate`."""
...
@staticmethod
def from_mc_gate(gate: MCGate, qubits: list[Qubit], label: str | None = ...) -> ValueOperation:
def from_mc_gate(
gate: MCGate, qubits: list[Qubit], label: str | None = ...
) -> ValueOperation:
"""Create while preserving parameters bound to an :class:`MCGate`."""
...
@staticmethod
Expand All @@ -154,6 +199,39 @@ class ValueOperation:
@property
def params(self) -> list[float | Parameter]: ...
@property
def name(self) -> str:
"""Human-readable instruction name for this operation."""
...
@property
def num_qubits(self) -> int:
"""Number of qubits used by this operation instance."""
...
@property
def num_params(self) -> int:
"""Number of parameters carried by this operation instance."""
...
@property
def instruction_type(self) -> str:
"""One of ``"standard"``, ``"mcgate"``, ``"unitary"``, ``"circuit"``,
``"directive"``, ``"classical_data"``, ``"classical_control"``, ``"delay"``."""
...
@property
def is_standard(self) -> bool: ...
@property
def is_mcgate(self) -> bool: ...
@property
def is_unitary(self) -> bool: ...
@property
def is_circuit_gate(self) -> bool: ...
@property
def is_directive(self) -> bool: ...
@property
def is_classical_data(self) -> bool: ...
@property
def is_classical_control(self) -> bool: ...
@property
def is_delay(self) -> bool: ...
@property
def label(self) -> str | None: ...
def matrix(self) -> NDArray[np.complex128]:
"""Compute the unitary matrix (fixed-parameter operations only).
Expand Down
6 changes: 1 addition & 5 deletions crates/binding-python/src/circuit/gate/mc_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,9 @@ impl PyMcGate {
///
/// The inverse of a controlled gate C(U) is C(U†).
///
/// # Arguments
///
/// * `params` - Optional parameters for the base gate.
///
/// # Returns
///
/// A tuple of (inverse gate, inverse parameters), or None if not invertible.
/// A new multi-controlled gate with inverse parameters already bound.
pub fn inverse(&self) -> PyResult<Self> {
if self.params.len() != self.inner.num_params() {
return Err(PyCircuitError::new_err(format!(
Expand Down
57 changes: 57 additions & 0 deletions crates/binding-python/src/circuit/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,63 @@ impl PyValueInstruction {
self.inner.is_instruction()
}

#[getter]
fn name(&self) -> String {
self.inner.name()
}

#[getter]
fn instruction_type(&self) -> &'static str {
self.inner.instruction_type()
}

#[getter]
fn is_standard(&self) -> bool {
self.inner.is_standard()
}

#[getter]
fn is_mcgate(&self) -> bool {
self.inner.is_mcgate()
}

#[getter]
fn is_unitary(&self) -> bool {
self.inner.is_unitary()
}

#[getter]
fn is_circuit_gate(&self) -> bool {
self.inner.is_circuit_gate()
}

#[getter]
fn is_directive(&self) -> bool {
self.inner.is_directive()
}

#[getter]
fn is_classical_data(&self) -> bool {
self.inner.is_classical_data()
}

#[getter]
fn is_delay(&self) -> bool {
self.inner.is_delay()
}

#[getter]
fn standard_gate(&self) -> Option<PyStandardGate> {
self.inner
.standard_gate()
.map(|gate| PyStandardGate::from(gate, vec![]))
}

#[getter]
fn directive(&self) -> Option<PyDirective> {
self.inner.directive().map(PyDirective::from)
}

#[getter]
fn instruction(&self) -> Option<PyInstruction> {
self.inner
Expand Down
60 changes: 60 additions & 0 deletions crates/binding-python/src/circuit/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,66 @@ impl PyValueOperation {
Ok(result)
}

#[getter]
fn name(&self) -> String {
self.inner.name()
}

#[getter]
fn num_qubits(&self) -> usize {
self.inner.num_qubits()
}

#[getter]
fn num_params(&self) -> usize {
self.inner.num_params()
}

#[getter]
fn instruction_type(&self) -> &'static str {
self.inner.instruction_type()
}

#[getter]
fn is_standard(&self) -> bool {
self.inner.is_standard()
}

#[getter]
fn is_mcgate(&self) -> bool {
self.inner.is_mcgate()
}

#[getter]
fn is_unitary(&self) -> bool {
self.inner.is_unitary()
}

#[getter]
fn is_circuit_gate(&self) -> bool {
self.inner.is_circuit_gate()
}

#[getter]
fn is_directive(&self) -> bool {
self.inner.is_directive()
}

#[getter]
fn is_classical_data(&self) -> bool {
self.inner.is_classical_data()
}

#[getter]
fn is_classical_control(&self) -> bool {
self.inner.is_classical_control()
}

#[getter]
fn is_delay(&self) -> bool {
self.inner.is_delay()
}

#[getter]
fn label(&self) -> Option<String> {
self.inner.label.as_ref().map(|s| s.to_string())
Expand Down
4 changes: 2 additions & 2 deletions crates/binding-python/src/compile/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ impl From<PyCompileMode> for CompileMode {
impl PyCompileMode {
pub(crate) fn repr_label(&self) -> &'static str {
match self.inner {
CompileMode::Normal => "CompileMode.normal()",
CompileMode::Enhanced => "CompileMode.enhanced()",
CompileMode::Normal => "CompileMode.Normal",
CompileMode::Enhanced => "CompileMode.Enhanced",
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/binding-python/src/device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub mod topology;
/// use pyo3::prelude::*;
/// use _native::device::register_device_module;
///
/// Python::with_gil(|py| {
/// Python::try_attach(|py| {
/// let module = PyModule::new(py, "cqlib").unwrap();
/// register_device_module(&module).unwrap();
/// });
Expand Down
26 changes: 18 additions & 8 deletions crates/binding-python/tests/test_compile_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ def test_workflow_types_are_public_compile_types() -> None:
assert CompilerWorkflow.__module__ == "cqlib.compile"
assert "CompileConfig" in compile_module.__all__
assert "CompilerWorkflow" in compile_module.__all__
assert repr(CompileMode.normal()) == "CompileMode.normal()"
assert repr(CompileMode.enhanced()) == "CompileMode.enhanced()"
assert repr(CompileMode.normal()) == "CompileMode.Normal"
assert repr(CompileMode.enhanced()) == "CompileMode.Enhanced"


def test_compiler_errors_are_public_compile_exceptions() -> None:
Expand Down Expand Up @@ -77,7 +77,7 @@ def test_compile_config_exposes_immutable_defaults_and_copy_protocol() -> None:
assert config.seed is None
assert copy.copy(config) is not config
assert copy.deepcopy(config) is not config
assert repr(config).startswith("CompileConfig(mode=CompileMode.normal(),")
assert repr(config).startswith("CompileConfig(mode=CompileMode.Normal,")

with pytest.raises(AttributeError):
config.seed = 3
Expand Down Expand Up @@ -143,10 +143,16 @@ def test_compile_and_explicit_workflow_have_equivalent_results() -> None:
direct = compile(circuit, target_basis=basis)
explicit = CompilerWorkflow(CompileConfig(target_basis=basis)).run(circuit)

direct_names = [str(operation.instruction) for operation in direct.circuit.operations]
explicit_names = [str(operation.instruction) for operation in explicit.circuit.operations]
direct_names = [
str(operation.instruction) for operation in direct.circuit.operations
]
explicit_names = [
str(operation.instruction) for operation in explicit.circuit.operations
]
assert direct_names == explicit_names == ["H", "CZ", "H"]
assert [step.name for step in direct.steps] == [step.name for step in explicit.steps]
assert [step.name for step in direct.steps] == [
step.name for step in explicit.steps
]
assert direct == explicit
assert direct.__eq__(object()) is NotImplemented

Expand Down Expand Up @@ -185,7 +191,9 @@ def test_workflow_validates_cross_field_configuration_when_run() -> None:
initial_layout=Layout.from_pairs([(0, 0)], physical_count=1),
)

with pytest.raises(CompilerConfigError, match="initial layout requires a target device"):
with pytest.raises(
CompilerConfigError, match="initial layout requires a target device"
):
CompilerWorkflow(config).run(Circuit(1))


Expand All @@ -197,5 +205,7 @@ def test_compile_config_rejects_unknown_target_gate_name() -> None:
def test_workflow_rejects_non_standard_target_instruction_when_run() -> None:
config = CompileConfig(target_basis=(Instruction.delay(),))

with pytest.raises(CompilerConfigError, match="unsupported workflow target instruction"):
with pytest.raises(
CompilerConfigError, match="unsupported workflow target instruction"
):
CompilerWorkflow(config).run(Circuit(1))
Loading
Loading