Skip to content
Open
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
45 changes: 45 additions & 0 deletions .agent/plans/fixed-parameter-targets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Fixed-parameter compiler targets

Status: complete.

## Goal and scope

Allow operation capabilities to restrict individual parameters to finite fixed
values. Unspecified parameters remain unrestricted. Target matching, serialized
attributes, synthesis-basis selection, and final verification must preserve the
same restrictions. Target compatibility also compares these constraints.
Symbolic values cannot satisfy a fixed parameter.

Derive synthesis from one arbitrary rotation axis and a fixed pulse about a
different axis. Support all distinct RX/RY/RZ pairs through cyclic coordinates.
Precompute an effective quarter-turn sequence from its actual angle; use native
half turns when available. Bound construction to 64 pulses per effective quarter
turn. Direct native-gate targets are a separate Core change.

## Decisions

Use optional fixed values per parameter, not a general constraint language.
Multiple capabilities describe alternative fixed values and placements. Match
constants with the existing absolute parameter-comparison tolerance, without
reducing angles modulo a period: doing so could lose global phase.

Only inspect parameter values for constrained capabilities. Unrestricted targets
keep their existing pipelines. Basis selection must never treat a fixed-angle
rotation as an arbitrary rotation.

Numeric and symbolic synthesis share one fixed-pulse recipe. Pulse-plan details
stay internal; Python exposes target capabilities and the selected basis kind.

## Validation

The compiler suite passed 235 tests; native synthesis passed 64 tests, including
1,944 full-matrix cases across all six axis pairs, both signs, fractional and
non-Clifford angles, optional half turns, and numeric or symbolic parameters.
Python target tests passed 386 cases, including numerical and symbolic input
gates. Generated stubs, repository lint, and full changed-file C++ lint passed.

## Follow-up

Direct GPI/GPI2, MS, and ZZ target support is separate work. It will own gate
conventions and synthesis in Core. The downstream adapter will then expose these
capabilities and Rigetti's fixed rotations.
42 changes: 31 additions & 11 deletions bindings/mlir/register_mlir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -811,37 +811,41 @@ either unrestricted or explicitly enumerated native-operation support.)pb");
std::optional<std::vector<mlir::CompilerTarget::SiteTuple>>
siteTuples,
const std::optional<uint64_t> duration,
const std::optional<double> fidelity) {
const std::optional<double> fidelity,
std::vector<std::optional<double>> fixedParameters) {
constructFromExpected(
self,
mlir::CompilerTarget::OperationCapability::create(
std::move(name), arity, numParameters,
std::move(siteTuples)
.value_or(
std::vector<mlir::CompilerTarget::SiteTuple>{}),
duration, fidelity));
duration, fidelity, std::move(fixedParameters)));
},
"name"_a, "arity"_a, "num_parameters"_a, "site_tuples"_a = nb::none(),
"duration"_a = nb::none(), "fidelity"_a = nb::none())
"duration"_a = nb::none(), "fidelity"_a = nb::none(), nb::kw_only(),
"fixed_parameters"_a = std::vector<std::optional<double>>{})
.def(
"__init__",
[](mlir::CompilerTarget::OperationCapability& self, std::string name,
const size_t arity, const size_t numParameters,
std::optional<std::vector<mlir::CompilerTarget::SiteTuple>>
siteTuples,
const std::optional<uint64_t> duration,
const std::optional<double> fidelity) {
const std::optional<double> fidelity,
std::vector<std::optional<double>> fixedParameters) {
constructFromExpected(
self,
mlir::CompilerTarget::OperationCapability::create(
std::move(name), arity, numParameters,
std::move(siteTuples)
.value_or(
std::vector<mlir::CompilerTarget::SiteTuple>{}),
duration, fidelity));
duration, fidelity, std::move(fixedParameters)));
},
"name"_a, "arity"_a, "num_parameters"_a, "site_tuples"_a = nb::none(),
"duration"_a = nb::none(), "fidelity"_a = nb::none())
"duration"_a = nb::none(), "fidelity"_a = nb::none(), nb::kw_only(),
"fixed_parameters"_a = std::vector<std::optional<double>>{})
.def_prop_ro(
"name",
[](const mlir::CompilerTarget::OperationCapability& operation) {
Expand All @@ -867,6 +871,15 @@ either unrestricted or explicitly enumerated native-operation support.)pb");
},
"Supported ordered placements with optional calibration; empty means "
"general applicability.")
.def_prop_ro(
"fixed_parameters",
[](const mlir::CompilerTarget::OperationCapability& operation) {
return std::vector<std::optional<double>>(
operation.fixedParameters().begin(),
operation.fixedParameters().end());
},
"Fixed values or None per parameter; empty means unrestricted. "
"Constants use absolute tolerance 1e-15 without angle wrapping.")
.def_prop_ro("duration",
&mlir::CompilerTarget::OperationCapability::duration,
"The raw default duration, if available.")
Expand Down Expand Up @@ -902,7 +915,9 @@ either unrestricted or explicitly enumerated native-operation support.)pb");
.value("XZX", mlir::CompilerTarget::SingleQubitBasis::XZX)
.value("XYX", mlir::CompilerTarget::SingleQubitBasis::XYX)
.value("ZYZ", mlir::CompilerTarget::SingleQubitBasis::ZYZ)
.value("ZXZ", mlir::CompilerTarget::SingleQubitBasis::ZXZ);
.value("ZXZ", mlir::CompilerTarget::SingleQubitBasis::ZXZ)
.value("FixedRotation",
mlir::CompilerTarget::SingleQubitBasis::FixedRotation);

auto synthesisBasis = nb::class_<mlir::CompilerTarget::SynthesisBasis>(
compilerTarget, "SynthesisBasis",
Expand Down Expand Up @@ -1142,15 +1157,20 @@ either unrestricted or explicitly enumerated native-operation support.)pb");
[](const mlir::CompilerTarget& target, const std::string_view name,
const size_t arity, const std::optional<size_t> numParameters,
const std::optional<std::vector<mlir::CompilerTarget::SiteId>>&
sites) {
sites,
const std::vector<std::optional<double>>& parameters) {
if (sites) {
return target.supportsOperation(name, arity, numParameters,
*sites);
*sites, parameters);
}
return target.supportsOperation(name, arity, numParameters);
return target.supportsOperation(name, arity, numParameters,
std::nullopt, parameters);
},
"name"_a, "arity"_a, "num_parameters"_a = nb::none(),
"sites"_a = nb::none(), "Whether the target supports an operation.");
"sites"_a = nb::none(), nb::kw_only(),
"parameters"_a = std::vector<std::optional<double>>{},
"Whether the target supports an operation. Omitted or None parameter "
"values require unrestricted support.");

nb::class_<mlir::TargetEnvironment>(
m, "TargetEnvironment",
Expand Down
2 changes: 2 additions & 0 deletions bindings/patterns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ mqt\.core\.mlir\.CompilerTarget\.OperationCapability\.__init__$:
site_tuples: Sequence[CompilerTarget.SiteTuple | Sequence[int]] | None = None,
duration: int | None = None,
fidelity: float | None = None,
*,
fixed_parameters: Sequence[float | None] = (),
) -> None:
\doc

Expand Down
2 changes: 1 addition & 1 deletion docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ compiler target
operation capability
**Preferred term:** operation capability. **Accepted aliases:** none. A
compiler target's description of a supported operation, including its name,
arity, parameters, placements, and optional calibration data. Represented by
arity, parameter count and optional fixed values, placements, and optional calibration data. Represented by
`CompilerTarget::OperationCapability` in C++ and
`CompilerTarget.OperationCapability` in Python. An MLIR operation is an IR
instance, not this capability description.
Expand Down
19 changes: 19 additions & 0 deletions docs/mlir/target_compilation.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,25 @@ placements without calibration in this list, and omit operations that are not
available anywhere. Structural and program-format constructs are not
compiler-target operations.

Restrict individual gate parameters with `fixed_parameters`. For example,
`CompilerTarget.OperationCapability("rx", 1, 1, fixed_parameters=[math.pi / 2])`
accepts only RX(π/2). A nonempty list has one entry per parameter; `None` leaves
that parameter unrestricted. Multiple capabilities can describe different fixed
values or placements. Constants match with absolute tolerance `1e-15`, without
angle wrapping; symbolic values do not match fixed values. Omit the list for
unrestricted parameters. The compiler derives a synthesis sequence from one
unrestricted rotation axis and a fixed angle about a different axis, available
on every site. Any distinct pair of RX, RY, and RZ is supported. This covers
positive and negative quarter turns, 45° pulses, and non-Clifford angles such as
0.37 radians. The sequence is computed once per target and reused for numeric
and symbolic input gates. Available native half turns shorten suitable
decompositions.

Zero and integer-π pulses do not supply the required mixing. The constructive
method also rejects angles that require more than 64 fixed pulses per effective
quarter turn, to bound circuit expansion. These restrictions affect synthesis;
matching fixed native operations remains available for every finite angle.

Use plain tuples for placements without calibration. Use
`CompilerTarget.SiteTuple([1, 0], duration=40, fidelity=0.99)` to attach
calibration to a placement; both forms can appear in the same list.
Expand Down
64 changes: 52 additions & 12 deletions mlir/include/mqt/Compiler/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"

#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
Expand Down Expand Up @@ -196,14 +197,16 @@ class CompilerTarget {
create(std::string name, size_t arity, size_t numParameters,
std::vector<SiteTuple> siteTuples = {},
std::optional<uint64_t> duration = std::nullopt,
std::optional<double> fidelity = std::nullopt);
std::optional<double> fidelity = std::nullopt,
std::vector<std::optional<double>> fixedParameters = {});

/// Create a validated operation capability.
[[nodiscard]] static llvm::Expected<OperationCapability>
create(std::string name, Arity arity, size_t numParameters,
std::vector<SiteTuple> siteTuples = {},
std::optional<uint64_t> duration = std::nullopt,
std::optional<double> fidelity = std::nullopt);
std::optional<double> fidelity = std::nullopt,
std::vector<std::optional<double>> fixedParameters = {});

/// Return the exact reported operation name.
[[nodiscard]] llvm::StringRef name() const noexcept;
Expand All @@ -217,6 +220,13 @@ class CompilerTarget {
/// Return the number of real-valued operation parameters.
[[nodiscard]] size_t numParameters() const noexcept;

/// Fixed parameter values; nullopt accepts any value. Empty is
/// unrestricted. Nonempty lists contain numParameters() entries. Constants
/// match with absolute tolerance 1e-15, without reducing angles modulo a
/// period.
[[nodiscard]] llvm::ArrayRef<std::optional<double>>
fixedParameters() const noexcept;

/// Return all supported ordered placements, or empty for general support.
[[nodiscard]] llvm::ArrayRef<SiteTuple> siteTuples() const noexcept;

Expand All @@ -231,12 +241,14 @@ class CompilerTarget {
Arity arity, size_t numParameters,
std::vector<SiteTuple> siteTuples,
std::optional<uint64_t> duration,
std::optional<double> fidelity);
std::optional<double> fidelity,
std::vector<std::optional<double>> fixedParameters);

std::string name_;
std::string canonicalName_;
Arity arity_;
size_t numParameters_;
std::vector<std::optional<double>> fixedParameters_;
std::vector<SiteTuple> siteTuples_;
std::optional<uint64_t> duration_;
std::optional<double> fidelity_;
Expand Down Expand Up @@ -292,19 +304,39 @@ class CompilerTarget {

/// Recognized globally usable single-qubit synthesis basis.
enum class SingleQubitBasis : uint8_t {
U, ///< `U(θ, φ, λ)`.
ZSXX, ///< `RZ` / `SX` / `X` synthesis via a ZYZ decomposition.
R, ///< XYX synthesis expressed with `R(θ, φ)`.
XZX, ///< `RX(φ) * RZ(θ) * RX(λ)`.
XYX, ///< `RX(φ) * RY(θ) * RX(λ)`.
ZYZ, ///< `RZ(φ) * RY(θ) * RZ(λ)`.
ZXZ, ///< `RZ(φ) * RX(θ) * RZ(λ)`.
U, ///< `U(θ, φ, λ)`.
ZSXX, ///< `RZ` / `SX` / `X` synthesis via a ZYZ decomposition.
R, ///< XYX synthesis expressed with `R(θ, φ)`.
XZX, ///< `RX(φ) * RZ(θ) * RX(λ)`.
XYX, ///< `RX(φ) * RY(θ) * RX(λ)`.
ZYZ, ///< `RZ(φ) * RY(θ) * RZ(λ)`.
ZXZ, ///< `RZ(φ) * RX(θ) * RZ(λ)`.
FixedRotation, ///< An arbitrary rotation and fixed pulses about another
///< axis.
};

/// Fixed pulse combined with arbitrary rotations about a distinct axis.
struct FixedRotationBasis {
GateKind gate;
GateKind freeGate;
double angle;
/// Free rotation angles before, between, and after fixed pulses.
/// Together they implement a local RX(π/2).
std::vector<double> quarterTurnAngles;
std::optional<double> halfTurnAngle;

/// Physical gates for local X/Y/Z; local Z is the free rotation axis.
[[nodiscard]] std::array<GateKind, 3> axes() const;

friend bool operator==(const FixedRotationBasis&,
const FixedRotationBasis&) = default;
};

/// One single-qubit basis and optional entangler usable across the target.
struct SynthesisBasis {
SingleQubitBasis singleQubit;
std::optional<GateKind> entangler;
std::optional<FixedRotationBasis> fixedRotation;

friend bool operator==(const SynthesisBasis&,
const SynthesisBasis&) = default;
Expand Down Expand Up @@ -390,16 +422,24 @@ class CompilerTarget {
/// Return operation capabilities in reported order.
[[nodiscard]] llvm::ArrayRef<OperationCapability> operations() const noexcept;

/// Return whether an operation capability is supported by the target.
/// Return whether an operation supports unrestricted parameter values.
[[nodiscard]] bool
supportsOperation(llvm::StringRef name, size_t arity,
std::optional<size_t> numParameters = std::nullopt) const;

/// Return whether an operation capability is supported on ordered sites.
/// Return whether an operation supports unrestricted values on ordered sites.
[[nodiscard]] bool supportsOperation(llvm::StringRef name, size_t arity,
std::optional<size_t> numParameters,
llvm::ArrayRef<SiteId> sites) const;

/// Check parameter values, optionally on ordered sites.
/// Unknown values require unrestricted support.
[[nodiscard]] bool
supportsOperation(llvm::StringRef name, size_t arity,
std::optional<size_t> numParameters,
std::optional<llvm::ArrayRef<SiteId>> sites,
llvm::ArrayRef<std::optional<double>> parameters) const;

/// Return whether a QCO operation is supported.
[[nodiscard]] bool supports(::mlir::Operation* operation) const;

Expand Down
7 changes: 5 additions & 2 deletions mlir/include/mqt/Dialect/MQT/IR/MQTDialect.td
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ def NativeOperationAttr : MQTAttr<"NativeOperation", "native_operation"> {
The operation records its spelling, arity, parameter count, and optional
global or site-specific calibration data. An empty site-tuple list means
general applicability; a nonempty list gives all supported ordered
placements. The following example records a directional controlled-X operation:
placements. Optional `fixed_parameters` contains one entry per parameter:
a finite f64 value for a fixed parameter, or `unit` for an unrestricted one.
The following example records a directional controlled-X operation:

```mlir
#mqt.native_operation<name = "cx",
Expand All @@ -195,7 +197,8 @@ def NativeOperationAttr : MQTAttr<"NativeOperation", "native_operation"> {
"uint64_t":$num_parameters,
MQTArrayRefParameter<"SiteTupleAttr">:$site_tuples,
MQTOptionalUInt64Parameter<>:$duration,
OptionalParameter<"FloatAttr">:$fidelity);
OptionalParameter<"FloatAttr">:$fidelity,
OptionalParameter<"ArrayAttr">:$fixed_parameters);
let assemblyFormat = "`<` struct(params) `>`";
let genVerifyDecl = 1;
}
Expand Down
20 changes: 12 additions & 8 deletions mlir/include/mqt/Dialect/QCO/Transforms/Decomposition/Euler.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ struct SynthesizedUnitary1Q {
};

/// Returns whether @p op belongs to @p basis.
[[nodiscard]] bool isSingleQubitBasisGate(Operation* op,
SingleQubitBasis basis);
/// Fixed-pulse bases require their target's @p fixedRotation descriptor.
[[nodiscard]] bool isSingleQubitBasisGate(
Operation* op, SingleQubitBasis basis,
const CompilerTarget::FixedRotationBasis* fixedRotation = nullptr);

/// Extracts `(theta, phi, lambda, phase)` of @p matrix in @p basis.
///
Expand All @@ -86,10 +88,10 @@ struct SynthesizedUnitary1Q {
/// @param basis The single-qubit synthesis basis.
/// @return The synthesized qubit and correction, or `std::nullopt` if synthesis
/// is skipped.
[[nodiscard]] std::optional<SynthesizedUnitary1Q>
synthesizeUnitary1QEuler(OpBuilder& builder, Location loc, Value qubit,
const Matrix2x2& composed, std::size_t runSize,
bool hasNonBasisGate, SingleQubitBasis basis);
[[nodiscard]] std::optional<SynthesizedUnitary1Q> synthesizeUnitary1QEuler(
OpBuilder& builder, Location loc, Value qubit, const Matrix2x2& composed,
std::size_t runSize, bool hasNonBasisGate, SingleQubitBasis basis,
const CompilerTarget::FixedRotationBasis* fixedRotation = nullptr);

/// Materializes one accumulated phase correction when needed.
///
Expand All @@ -104,10 +106,12 @@ void emitGPhaseIfNeeded(OpBuilder& builder, Location loc, double phase);
/// Synthesizes one supported runtime-parameterized operation in @p basis.
///
/// Leaves operations that already belong to @p basis unchanged.
/// Fixed-pulse bases require their target's @p fixedRotation descriptor.
///
/// @pre `canSynthesizeParameterizedUnitary1Q(op)` is true.
void synthesizeParameterizedUnitary1Q(RewriterBase& rewriter, Operation* op,
SingleQubitBasis basis);
void synthesizeParameterizedUnitary1Q(
RewriterBase& rewriter, Operation* op, SingleQubitBasis basis,
const CompilerTarget::FixedRotationBasis* fixedRotation = nullptr);

/// Populates @p patterns with the single-qubit run fusion rewrite for
/// @p basis (the reusable core of `fuse-single-qubit-unitary-runs`).
Expand Down
1 change: 1 addition & 0 deletions mlir/lib/Compiler/QDMIAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,7 @@ static bool sameOperation(const CompilerTarget::OperationCapability& lhs,
if (lhs.canonicalName() != rhs.canonicalName() ||
lhs.arity() != rhs.arity() ||
lhs.numParameters() != rhs.numParameters() ||
lhs.fixedParameters() != rhs.fixedParameters() ||
lhs.siteTuples().size() != rhs.siteTuples().size()) {
return false;
}
Expand Down
Loading
Loading