Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
140 commits
Select commit Hold shift + click to select a range
64636a0
Regenerate doc tests and prune stale generated files
ciaranra May 13, 2026
0f58884
Auto-include CUDA Python packages when toolkit and NVIDIA GPU detected
ciaranra May 13, 2026
9740a15
Merge remote-tracking branch 'origin/dev' into cleanup
ciaranra May 14, 2026
6a55986
Surface CUDA Python packages in pecos setup summary
ciaranra May 14, 2026
1f525cc
Stop pytest from shelling out to cargo, fix shadowed slr_tests module…
ciaranra May 14, 2026
7cbcb5c
Sanitize generated doc test paths and fix cmake-setup example
ciaranra May 14, 2026
8796251
Introduce VariableState; fix SLR linearity bugs at use-after-unpack s…
ciaranra May 14, 2026
0c4b0c0
Add AST -> Guppy v1 acceptance tests as xfail spec
ciaranra May 14, 2026
d1eea61
Tighten v1 acceptance tests: strict xfail; defer rejection tests
ciaranra May 14, 2026
626dbf0
Add Guppy-only linearity helper for AST -> Guppy lowering
ciaranra May 14, 2026
08f94a4
Rewrite AST Guppy emitter for v1 linearity
ciaranra May 14, 2026
6905ae6
Clean up legacy AST Guppy tests after v1 emitter rewrite
ciaranra May 14, 2026
8c55962
Tighten _harness lint: TYPE_CHECKING import, Error suffix on exception
ciaranra May 14, 2026
fbf4746
Black formatting on AST -> Guppy v1 work
ciaranra May 14, 2026
539d21d
Fix Guppy bool emission for CReg bit assignments
ciaranra May 14, 2026
d665536
Add Selene behavioral test harness for AST -> Guppy v1 (Workstream A)
ciaranra May 14, 2026
dc8cb65
Workstream B audit runner skeleton (waiting on _force_ast kwarg)
ciaranra May 14, 2026
d730c52
Add audit-only AST HUGR route
ciaranra May 14, 2026
7ed21f3
Expand audit runner with legacy HUGR test corpus (9 cases all pass)
ciaranra May 14, 2026
ac61737
Harden AST Guppy behavioral coverage
ciaranra May 14, 2026
3e7b5a1
Add examples/ corpus to audit runner (pass 2)
ciaranra May 15, 2026
21b80b6
Add qeclib corpus + ExpectedFailure XFAIL mechanism to audit runner (…
ciaranra May 15, 2026
6a340bd
Harden Guppy v1 preflight + inline-CReg inference; lock down pass 4 a…
ciaranra May 15, 2026
48ce554
Cutover: route SlrConverter.hugr() through AST by default; remove _fo…
ciaranra May 15, 2026
78e3af1
Cutover: delete legacy gen_codes/guppy/ and dependent tests
ciaranra May 15, 2026
d849669
Apply Codex review fixes: no-arg entrypoint wrap + compile diagnostic…
ciaranra May 15, 2026
c0c59c7
Fix Codex review v2 findings: entry wrapper mirrors emitter return sh…
ciaranra May 15, 2026
139912b
Mirror emitter for non-result-CReg Return values; cover nested-contro…
ciaranra May 15, 2026
bb58b52
Widen compile-error diagnostics to module-load failures; factor out _…
ciaranra May 15, 2026
975768b
v2 Phase 1: add Print(value, *, tag=None, namespace='result') with SL…
ciaranra May 15, 2026
fc82ea7
Phase 1 Print: apply Codex tracer-bullet review (validate derived tag…
ciaranra May 15, 2026
f2ebb32
Phase 1 Print: path-signature validator rejects asymmetric If branche…
ciaranra May 15, 2026
517f1fc
Phase 1 Print: inline-CReg definite-assignment dataflow validator + 8…
ciaranra May 15, 2026
92eec4d
Phase 1 Print: tighten inline-CReg validator to bit-level (Codex trac…
ciaranra May 15, 2026
6efe8c3
Phase 1 Print: reject whole-CReg Print of inline CReg outright (Codex…
ciaranra May 15, 2026
95a36ee
Phase 1 Print: adversarial tests (cross-codegen byte-identity + Selen…
ciaranra May 15, 2026
068bba3
Phase 2 deprecation warnings: CReg(result=False) construction-time wa…
ciaranra May 15, 2026
6f3dc61
Land Phase 3a.0-3a.3 iters 1-4: BlockDecl/BlockCall AST + Guppy emitt…
ciaranra May 15, 2026
c5fe5e1
Merge phase2-deprecation-warnings: DeprecationWarning for CReg(result…
ciaranra May 15, 2026
e6931d3
Merge phase1-print: Print(value, *, tag=None, namespace='result') AST…
ciaranra May 15, 2026
ec3bedb
Phase 3a.3 iter 5a-prep: typed BlockArg sum type for BlockCall arg/ou…
ciaranra May 15, 2026
0f71cfc
Fix Codex review of BlockArg refactor: flatten path now rejects defer…
ciaranra May 15, 2026
eaa7a6f
Phase 3a.3 iter 5b: single-qubit BlockInput type + SingleQubitArg cal…
ciaranra May 15, 2026
2455747
Fix Codex iter-5b review: reject mismatched arg/out bindings for LIVE…
ciaranra May 15, 2026
b650289
Phase 3a.3 iter 5c: single-bit BlockInput via array[bool,1] write-bac…
ciaranra May 15, 2026
1533925
Phase 3a.3 iter 5d: QubitBundleArg non-contiguous slot-bundle call-si…
ciaranra May 16, 2026
f64ad09
Iter-5d Codex polish: end-to-end Selene lock-in for cross-allocator b…
ciaranra May 16, 2026
490f483
Iter-5d r2 polish: add asymmetric bundle test that genuinely pins unp…
ciaranra May 16, 2026
74ea9cc
Phase 3a.3 iter 5e.1: unify BlockDecl-body substitution into shared B…
ciaranra May 16, 2026
bcb04eb
Iter-5e.1 Codex early-fixes: token-boundary permute reject (no substr…
ciaranra May 16, 2026
9c9f7d8
Phase 3a.3 iter 5e.2: SLR var->typed BlockArg detection (single Qubit…
ciaranra May 16, 2026
45dc0b6
Scratch-ancilla S1: ResourceEffect.SCRATCH + converter detection (exc…
ciaranra May 16, 2026
eb0b987
Scratch-ancilla S1: reject any ReturnOp in a scratch-bearing block (R…
ciaranra May 16, 2026
a2464c0
Scratch-ancilla S2: Guppy emitter lowers SCRATCH as an internal qubit…
ciaranra May 16, 2026
2dc9324
Scratch-ancilla S2 fixes (Codex r1): whole-program guard rejecting no…
ciaranra May 16, 2026
6accde4
Scratch-ancilla S2 R5-completeness (Codex r2): per-scope purity guard…
ciaranra May 16, 2026
f1755c4
Scratch-ancilla S4: convert qeclib Check (a:scratch) -- SynExtractBar…
ciaranra May 16, 2026
4c9d071
Scratch-ancilla S4: add Color488 serial SynExtractBare production loc…
ciaranra May 16, 2026
89d824c
Scratch-ancilla S5: convert qeclib Check1Flag (a,flag:scratch; Prep(a…
ciaranra May 16, 2026
208fd95
Scratch-ancilla S5: fix corpus tracked-block table doc drift (Check/C…
ciaranra May 16, 2026
b1308ed
v1 cutover cleanup: port surface_code_slr_exploration notebook off th…
ciaranra May 16, 2026
4cfe2e6
Centralize AST visitor dispatch: replace 37 per-node accept() double-…
ciaranra May 16, 2026
163b29f
Visitor dispatch: walk type(node).__mro__ so subclasses of concrete A…
ciaranra May 16, 2026
25ffeaa
Fix two pre-existing qeclib construction bugs (audit XFAIL->accepted,…
ciaranra May 16, 2026
9bb2a7a
Add Phase 3b S1 codemod (libcst): append explicit Return(<result CReg…
ciaranra May 16, 2026
db9cff3
Phase 3b S1a: generalize _selene_harness to accept explicit Return (d…
ciaranra May 16, 2026
5c0fe73
Phase 3b gate-hardening: implicit-return warning detector now also ca…
ciaranra May 16, 2026
7fef59d
Phase 3b S1b: harden implicit-return detector (recursive cout -- whol…
ciaranra May 16, 2026
0c0df3d
Phase 3b S1c: fix logical_steane_code_program example -- stale pecos.…
ciaranra May 16, 2026
4c9f53d
Phase 3b S2: remove Phase-2 implicit-return warning machinery (slr_co…
ciaranra May 16, 2026
d03fd91
Phase 3b S4: port qir_bc() to AST path (binding.parse_assembly bitcod…
ciaranra May 16, 2026
86d7ef3
Phase 3b S4 fix: wrap binding.parse_assembly().as_bitcode() in the di…
ciaranra May 16, 2026
43a7835
Phase 3b S3: remove CReg(result=) kwarg + RegisterDecl.is_result fiel…
ciaranra May 16, 2026
93deecb
Phase 3b S5: M2 converter-time elision of flattened composite block-b…
ciaranra May 16, 2026
756cd8f
#72: fix _selene_harness measurement-mapping via opt-in named return …
ciaranra May 17, 2026
030b9d1
#72 close-out: fail-loud on unexpected Selene result-tag shape in _sh…
ciaranra May 17, 2026
1301f73
#71 Stage A: add qir-qis test-group dep + QIR spec-compliance baselin…
ciaranra May 17, 2026
08b901c
#71 Stage B1: emit required QIR module metadata in _finalize_module (…
ciaranra May 17, 2026
29fa4b4
#71 B2a: declare standard QIR classical-model functions (__quantum__q…
ciaranra May 17, 2026
9c2ca82
#75: extend pecos_rslib_llvm with LLVM memory ops to unblock #71 B2 -…
ciaranra May 17, 2026
b396d39
#75 post-review nit #1 (Pickle): type __richcmp__ ordering ops (Lt/Le…
ciaranra May 17, 2026
bf89b7f
#71 B2 (B2b+B2c) on the #75-extended binding: replace bespoke CReg ru…
ciaranra May 17, 2026
b4c3a9c
#71 B2 post-review fold: fix Codex blocker + 2 non-blocking. BLOCKER:…
ciaranra May 17, 2026
eb163ea
#71 B2 Codex re-confirm fold (cosmetic): raise the >64-bit CReg NotIm…
ciaranra May 17, 2026
1c67c3a
#74: fail loud on the 3 silent AST->QIR miscompiles (B3). VarExpr (wa…
ciaranra May 17, 2026
177ab70
#74 dual-review fold (Codex blocker): fix the 4th silent AST->QIR mis…
ciaranra May 17, 2026
643add5
#74 Codex re-confirm fold: satisfy the project formatter gate (the on…
ciaranra May 17, 2026
41cd9ea
#73 pre-PR hygiene: resolve branch-wide ruff/black debt -> repo fully…
ciaranra May 17, 2026
a3e9124
#78: fix the silent AST->QIR-sibling miscompiles in the Stim + Quantu…
ciaranra May 17, 2026
abfc8c0
#77 DONE: real executable QIR->QIS->selene Tier-2 differential (A+B+C…
ciaranra May 17, 2026
92961d7
#77 dual-review fold (Codex blocker + 2 non-blocking; Pickle YES). BL…
ciaranra May 17, 2026
10cf23e
#80: fail loud on the 2 QIR silent-miscompile bugs #79 pre-review sur…
ciaranra May 18, 2026
259e066
#80 dual-review fold: fix Codex blocker (Return-only inline CReg sile…
ciaranra May 18, 2026
62ac760
#80 Codex re-confirm-1 fold: fix the Return-only inline-CReg name-col…
ciaranra May 18, 2026
f85f2ed
#81 Stage A: PrepareOp.basis discriminant + 6 dedicated prep classes …
ciaranra May 18, 2026
8a2a55c
#81 Stage B: single shared canonical basis->tail map (_prep_tail.py) …
ciaranra May 18, 2026
02d152d
#81 Stage C: hard-replace Prep->PZ repo-wide (63 files, all 3 subsyst…
ciaranra May 18, 2026
c9cec69
#81 Stage D: docs->dedicated PZ/PX + doc-tests regen (22/0); audit fa…
ciaranra May 18, 2026
c638031
#81 Stage E: behavioral suite test_prep_gates.py -- 6 dedicated prep …
ciaranra May 18, 2026
e7deda1
#81 post-review fold (round-1): Codex blocker -- direct canonical PNX…
ciaranra May 18, 2026
0fb673a
#87/#88 partial: AST-QIR optional_dependency-lane fixes (honest, pre-…
ciaranra May 18, 2026
fc3468a
#87 resolved: real Permute realization in AST QIR (static logical rel…
ciaranra May 19, 2026
42610a9
#88: broad unsupported-gate fail-loud (88A, the class #78 deferred) +…
ciaranra May 19, 2026
26ecfb5
#79: corpus-wide executable QIR validation (generalises #77 Layer D)
ciaranra May 19, 2026
1718733
#87 post-review fold (Codex blocker): validate expanded Permute refs …
ciaranra May 19, 2026
663c0f7
Strip reviewer/persona names from code comments; pin duplicate-target…
ciaranra May 19, 2026
e7eed17
#93 (partial): verified QIR lowering for SX/SXdg/SY/SYdg (executable-…
ciaranra May 19, 2026
8acfa57
Fix dead test module: rename tier2_semantic.py -> test_tier2_semantic…
ciaranra May 19, 2026
e475028
A2: fix the Permute silent-miscompile in the Stim + QuantumCircuit co…
ciaranra May 19, 2026
1806f43
A: Stim unsupported-gate silent-skip -> fail loud (#78 surfaced-not-c…
ciaranra May 19, 2026
3a17c97
Post-review fold (Codex blocker): complete the branch-wide persona/pr…
ciaranra May 19, 2026
ae7ba85
#93 (cont.): verified QIR lowering for the F/Fdg/F4/F4dg face Cliffords
ciaranra May 19, 2026
ca0c6bf
#95: canonicalize GateKind on SZ/SZdg; remove redundant S/Sdg (user-d…
ciaranra May 19, 2026
9f797cc
test hygiene: dedupe the repeated QC expected-string into a local (be…
ciaranra May 19, 2026
c5cb87e
#93 cont.: verified QIR lowering for SZZ/SZZdg/SXX/SXXdg/SYY/SYYdg/CY…
ciaranra May 20, 2026
469f6b3
#93 post-review fold (Codex blocker): add phase-sensitive CY interfer…
ciaranra May 20, 2026
d8c98ed
#93 re-confirm fold (Codex blocker): COM812 trailing comma on the new…
ciaranra May 20, 2026
1087e4b
#93 cont.: verified 1-CX QIR lowering for CH (non-Clifford -- Quest b…
ciaranra May 20, 2026
cbc8bb1
#93 cont.: verified 1-RZZ QIR lowering for CRX/CRY/CRZ + PECOS oracles
ciaranra May 20, 2026
821b587
#88B: Guppy slot-local name disambiguation against declared register …
ciaranra May 20, 2026
6f074e3
Cross-codegen: verified Stim decomposition for F/Fdg/F4/F4dg face Cli…
ciaranra May 20, 2026
9c5fa59
Cross-codegen: native CRX/CRY/CRZ in ArbitraryRotationGateable + QC p…
ciaranra May 20, 2026
d8b18fc
Cross-codegen: CRX/CRY/CRZ in CuStateVec/CudaStateVec/MPS simulator b…
ciaranra May 20, 2026
b13987c
Cross-codegen post-review fold (Codex blocker): CR* control-superposi…
ciaranra May 21, 2026
e4768f9
#94 B1: classical-variable (whole-CReg scalar) QIR lowering via share…
ciaranra May 21, 2026
0b6560d
Cross-codegen Guppy Phase A: native SX/SXdg (v/vdg) + RX/RY/RZ/CRZ ro…
ciaranra May 21, 2026
922326e
Cross-codegen Guppy Phase B: decompose SY/SYdg/F-family/sqrt-Paulis/C…
ciaranra May 21, 2026
67714c5
Post-review folds (Codex non-blockers): Guppy decomposed-rotation mis…
ciaranra May 21, 2026
b8fbb9b
#97: SLR API -- rotational gates use angle-first RX(theta, q), remove…
ciaranra May 21, 2026
64c66f7
#97 followup: fix stale test_control_flow_qir comment (rx now lowers;…
ciaranra May 21, 2026
77b149d
#97 post-review fold (Codex blocker): fail loud on mis-ordered angle-…
ciaranra May 21, 2026
d826e96
#97 post-review fold: narrow qarg guard to quantum qubit types (Codex…
ciaranra May 21, 2026
76e2d4c
Add SLR v2 typed-angle API: rotation gates take rad()/turns() over th…
ciaranra May 21, 2026
fea3d0b
Merge origin/dev: keep AST-only SLR Guppy path, drop superseded legac…
ciaranra May 22, 2026
1aa707b
Make just lint pass: typos false-positive exemptions, black 26.x refo…
ciaranra May 22, 2026
081f922
Harden GHA pinning check: bind the 40-hex SHA to the uses: ref so a c…
ciaranra May 22, 2026
fa0a643
Strip internal planning/issue references and persona names from code …
ciaranra May 22, 2026
3f952ff
Strip remaining internal stage labels and string-literal planning ref…
ciaranra May 22, 2026
1a2ad0c
Strip remaining internal milestone labels from comment and message st…
ciaranra May 22, 2026
54ad323
Strip milestone Phase/iter references from comments and messages, kee…
ciaranra May 22, 2026
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
11 changes: 10 additions & 1 deletion .typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ IY = "IY"
IZ = "IZ"
iz = "iz"
anc = "anc"
Pn = "Pn"
emiss = "emiss"
fo = "fo"
# HUGR JSON format uses "typ" as a field name for type information
Expand Down Expand Up @@ -42,3 +41,13 @@ agger = "agger"
CPY = "CPY"
# Abbreviation for "undetectable" in fault enumeration debug output
UNDET = "UNDET"
# Legitimate "mis-" prefix used in comments/messages (mis-ordered,
# mis-declared, mis-emitted, mis-shape, mis-count, mis-mapping, ...)
mis = "mis"
# Valid spelling of "unparseable" (used in comments + a test name)
unparseable = "unparseable"
Unparseable = "Unparseable"
# Prep-gate naming: the "Pn -> PN" entry-point rename (PNX/PNY/PNZ)
Pn = "Pn"
PN = "PN"
pn = "pn"
69 changes: 69 additions & 0 deletions crates/pecos-core/src/angle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,46 @@ where
}
}

/// Converts the angle to turns in `[0, 1)` (the inverse of `from_turns`).
///
/// # Panics
/// This function will panic if the conversion of `fraction` or `max_value` to `f64` fails.
pub fn to_turns(&self) -> f64 {
let max_value = T::max_value()
.to_f64()
.expect("Failed to convert max_value to f64");
self.fraction
.to_f64()
.expect("Failed to convert fraction to f64")
/ max_value
}

/// Converts the angle to signed turns in `(-0.5, 0.5]`.
pub fn to_turns_signed(&self) -> f64 {
let t = self.to_turns();
if t > 0.5 { t - 1.0 } else { t }
}

/// Converts the angle to half-turns in `[0, 2)` (π radians = 1 half-turn).
///
/// Half-turns are the unit used by some backends (e.g. Guppy's `angle`),
/// where a full turn is `2.0`.
///
/// # Panics
/// This function will panic if the conversion of `fraction` or `max_value` to `f64` fails.
pub fn to_half_turns(&self) -> f64 {
self.to_turns() * 2.0
}

/// Converts the angle to signed half-turns in `(-1, 1]`.
///
/// Like [`Self::to_radians_signed`], this principal-value form avoids the
/// spurious global phase that the unsigned `[0, 2)` form introduces when a
/// half-angle (`θ/2`) computation crosses the 2π wrap point.
pub fn to_half_turns_signed(&self) -> f64 {
self.to_turns_signed() * 2.0
}

/// Creates an angle from a value in radians.
///
/// # Panics
Expand Down Expand Up @@ -668,6 +708,35 @@ mod tests {
use rand::RngExt;
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI, TAU};

#[test]
fn test_to_turns_and_half_turns() {
// Quarter turn = pi/2 rad = 0.25 turns = 0.5 half-turns.
let q = Angle64::QUARTER_TURN;
assert!((q.to_turns() - 0.25).abs() < 1e-12);
assert!((q.to_half_turns() - 0.5).abs() < 1e-12);
assert!((q.to_turns_signed() - 0.25).abs() < 1e-12);
assert!((q.to_half_turns_signed() - 0.5).abs() < 1e-12);

// Half-turn boundary: to_turns stays unsigned (0.5), signed maps to 0.5.
let h = Angle64::HALF_TURN;
assert!((h.to_turns() - 0.5).abs() < 1e-12);
assert!((h.to_half_turns() - 1.0).abs() < 1e-12);

// Three-quarter turn: unsigned 0.75 turns; signed wraps to -0.25 turns
// (-0.5 half-turns), mirroring to_radians_signed.
let tq = Angle64::THREE_QUARTERS_TURN;
assert!((tq.to_turns() - 0.75).abs() < 1e-12);
assert!((tq.to_turns_signed() - (-0.25)).abs() < 1e-12);
assert!((tq.to_half_turns_signed() - (-0.5)).abs() < 1e-12);

// half-turns == radians / pi for the unsigned form.
let a = Angle64::from_radians(0.7);
assert!((a.to_half_turns() - a.to_radians() / PI).abs() < 1e-9);
// round-trip turns.
let b = Angle64::from_turns(0.3);
assert!((b.to_turns() - 0.3).abs() < 1e-9);
}

// Basic Construction and Properties
#[test]
fn test_constructors() {
Expand Down
6 changes: 3 additions & 3 deletions crates/pecos-core/src/gate_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ pub enum GateType {
// TODO: MPauli instead of the other variants?

// PX = 130
// PnX = 131
// PNX = 131
// PY = 132
// PnY = 133
// PNY = 133
// PZ = 134
PZ = 134,
// PnZ
// PNZ
/// Allocate a qubit in the |0⟩ state
QAlloc = 135,
/// Free/deallocate a qubit
Expand Down
148 changes: 147 additions & 1 deletion crates/pecos-llvm/src/llvm_compat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ impl<'ctx> LLFunctionType<'ctx> {
}

/// Wrapper for LLVM types that mirrors llvmlite's type hierarchy
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum LLType<'ctx> {
Void,
Int(IntType<'ctx>),
Expand All @@ -236,6 +236,38 @@ pub enum LLType<'ctx> {
Array(ArrayType<'ctx>),
}

// inkwell 0.8.0 only derives `Hash` for `IntType`; the other type wrappers
// are `Eq` (LLVM type-ref pointer equality) but not `Hash`. Hash the same
// `LLVMTypeRef` pointer so `Hash` stays consistent with that `Eq`.
impl std::hash::Hash for LLType<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
use inkwell::types::AsTypeRef;
match self {
LLType::Void => 0u8.hash(state),
LLType::Int(t) => {
1u8.hash(state);
(t.as_type_ref() as usize).hash(state);
}
LLType::Float(t) => {
2u8.hash(state);
(t.as_type_ref() as usize).hash(state);
}
LLType::Pointer(t) => {
3u8.hash(state);
(t.as_type_ref() as usize).hash(state);
}
LLType::Struct(t) => {
4u8.hash(state);
(t.as_type_ref() as usize).hash(state);
}
LLType::Array(t) => {
5u8.hash(state);
(t.as_type_ref() as usize).hash(state);
}
}
}
}

impl<'ctx> LLType<'ctx> {
/// Create void type
#[must_use]
Expand Down Expand Up @@ -717,6 +749,106 @@ impl<'ctx> LLIRBuilder<'ctx> {
Ok(LLValue::Pointer(result))
}
}

// ========================================================================
// Memory ops + casts (unblocks the standard CReg model)
// ========================================================================

/// `alloca <ty>` -- stack slot. Caller positions the builder (B2
/// places `CReg` buffers in the entry block via `position_at_end`).
pub fn alloca(&self, ll_type: LLType<'ctx>, name: &str) -> LLResult<LLValue<'ctx>> {
let basic_ty = ll_type
.to_basic_metadata_type()
.ok_or_else(|| PecosError::Generic("Cannot alloca a void type".into()))?;
let result = self
.builder
.build_alloca(basic_ty, name)
.map_err(|e| PecosError::Generic(format!("Failed to build alloca: {e}")))?;
Ok(LLValue::Pointer(result))
}

/// `load` (LLVM-14 typed pointer: pointee inferred from `ptr`).
pub fn load(&self, ptr: LLValue<'ctx>, name: &str) -> LLResult<LLValue<'ctx>> {
let result = self
.builder
.build_load(ptr.as_pointer_value(), name)
.map_err(|e| PecosError::Generic(format!("Failed to build load: {e}")))?;
Ok(match result {
BasicValueEnum::IntValue(v) => LLValue::Int(v),
BasicValueEnum::FloatValue(v) => LLValue::Float(v),
BasicValueEnum::PointerValue(v) => LLValue::Pointer(v),
BasicValueEnum::ArrayValue(v) => LLValue::Array(v),
other => {
return Err(PecosError::Generic(format!(
"load: unsupported loaded value type: {other:?}"
)));
}
})
}

/// `store` -- discards inkwell's returned pointer (Python `-> None`).
pub fn store(&self, ptr: LLValue<'ctx>, value: LLValue<'ctx>) -> LLResult<()> {
self.builder
.build_store(ptr.as_pointer_value(), value.to_basic_value())
.map_err(|e| PecosError::Generic(format!("Failed to build store: {e}")))?;
Ok(())
}

/// `zext` int value to a wider int type.
pub fn zext(
&self,
value: LLValue<'ctx>,
dest_type: LLType<'ctx>,
name: &str,
) -> LLResult<LLValue<'ctx>> {
let result = self
.builder
.build_int_z_extend(value.as_int_value(), dest_type.as_int_type(), name)
.map_err(|e| PecosError::Generic(format!("Failed to build zext: {e}")))?;
Ok(LLValue::Int(result))
}

/// `trunc` int value to a narrower int type.
pub fn trunc(
&self,
value: LLValue<'ctx>,
dest_type: LLType<'ctx>,
name: &str,
) -> LLResult<LLValue<'ctx>> {
let result = self
.builder
.build_int_truncate(value.as_int_value(), dest_type.as_int_type(), name)
.map_err(|e| PecosError::Generic(format!("Failed to build trunc: {e}")))?;
Ok(LLValue::Int(result))
}

/// Unsigned integer comparison (mirrors `icmp_signed` with U-predicates).
pub fn icmp_unsigned(
&self,
op: &str,
lhs: LLValue<'ctx>,
rhs: LLValue<'ctx>,
name: &str,
) -> LLResult<LLValue<'ctx>> {
let predicate = match op {
"==" => IntPredicate::EQ,
"!=" => IntPredicate::NE,
"<" => IntPredicate::ULT,
">" => IntPredicate::UGT,
"<=" => IntPredicate::ULE,
">=" => IntPredicate::UGE,
_ => {
return Err(PecosError::Generic(format!(
"Unknown comparison operator: {op}"
)));
}
};
let result = self
.builder
.build_int_compare(predicate, lhs.as_int_value(), rhs.as_int_value(), name)
.map_err(|e| PecosError::Generic(format!("Failed to build icmp: {e}")))?;
Ok(LLValue::Int(result))
}
}

// ============================================================================
Expand Down Expand Up @@ -766,4 +898,18 @@ impl LLConstant {
)),
}
}

/// Zero/`zeroinitializer` constant of `ll_type` (backs
/// `Constant(ty, None)`; Array -> `zeroinitializer`, Int -> `iN 0`).
pub fn zero(ll_type: LLType<'_>) -> LLResult<LLValue<'_>> {
match ll_type {
LLType::Int(t) => Ok(LLValue::Int(t.const_zero())),
LLType::Float(t) => Ok(LLValue::Float(t.const_zero())),
LLType::Pointer(t) => Ok(LLValue::Pointer(t.const_zero())),
LLType::Array(t) => Ok(LLValue::Array(t.const_zero())),
LLType::Void | LLType::Struct(_) => Err(PecosError::Generic(
"Cannot create a zero constant for void/struct type".to_string(),
)),
}
}
}
70 changes: 70 additions & 0 deletions crates/pecos-simulators/src/arbitrary_rotation_gateable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,76 @@ pub trait ArbitraryRotationGateable: CliffordGateable {
self.rxx(theta, pairs).ryy(phi, pairs).rzz(lambda, pairs)
}

/// Applies a controlled-RZ rotation: target qubit gets RZ(theta) when control = |1>.
///
/// `CRZ(theta) = block-diag(I, RZ(theta)) = diag(1, 1, exp(-i*theta/2), exp(i*theta/2))`.
///
/// Default 2q-minimal decomposition (1 RZZ + 1 single-qubit RZ on the
/// target): `CRZ(theta) = (I o RZ(theta/2)) . RZZ(-theta/2)`.
/// Verified: with the trait's `RZ = exp(-i*theta/2*Z)` and `RZZ =
/// exp(-i*theta/2*Z*Z)` conventions, the product on the c=0 sector
/// gives `RZ(theta/2) . exp(i*theta/4*I) = I` up to global phase, and
/// on c=1 (where ZZ acts as -Z on target) gives `RZ(theta/2) . X .
/// RZ(theta/2) . X = RZ(theta)` -- i.e. the convention-1 controlled
/// rotation. The non-PECOS-prefactor convention requires no extra
/// RZ on the control.
///
/// # Parameters
/// - `theta`: The rotation angle on the target.
/// - `pairs`: Pairs of qubit indices `[(control, target), ...]`.
///
/// # Returns
/// A mutable reference to `Self` for method chaining.
#[inline]
fn crz(&mut self, theta: Angle64, pairs: &[(QubitId, QubitId)]) -> &mut Self {
// Half-angle first, THEN negate -- `Angle<T>` is a wrapping fraction
// of a full turn (modulo 2pi), so `-theta / 2` would halve the wrapped
// 2*pi - theta and produce pi - theta/2, not -theta/2.
let half = theta / 2u64;
let targets: QubitBuf = pairs.iter().map(|&(_, t)| t).collect();
self.rzz(-half, pairs).rz(half, &targets)
}

/// Applies a controlled-RX rotation: target qubit gets RX(theta) when control = |1>.
///
/// Default decomposition: `CRX(theta) = (I o H) . CRZ(theta) . (I o H)`,
/// using `H.Z.H = X` so the c=1 sector applies `H.RZ(theta).H = RX(theta)`.
/// Same 2q cost as `crz` (1 RZZ).
///
/// # Parameters
/// - `theta`: The rotation angle on the target.
/// - `pairs`: Pairs of qubit indices `[(control, target), ...]`.
///
/// # Returns
/// A mutable reference to `Self` for method chaining.
#[inline]
fn crx(&mut self, theta: Angle64, pairs: &[(QubitId, QubitId)]) -> &mut Self {
let targets: QubitBuf = pairs.iter().map(|&(_, t)| t).collect();
self.h(&targets).crz(theta, pairs).h(&targets)
}

/// Applies a controlled-RY rotation: target qubit gets RY(theta) when control = |1>.
///
/// Default decomposition: `CRY(theta) = (I o S.H) . CRZ(theta) . (I o H.Sdg)`,
/// using `S.X.Sdg = Y` (so `S.Rx.Sdg = Ry`) and `H.Rz.H = Rx`, giving
/// `S.H.RZ.H.Sdg = RY`. Same 2q cost as `crz` (1 RZZ).
///
/// # Parameters
/// - `theta`: The rotation angle on the target.
/// - `pairs`: Pairs of qubit indices `[(control, target), ...]`.
///
/// # Returns
/// A mutable reference to `Self` for method chaining.
#[inline]
fn cry(&mut self, theta: Angle64, pairs: &[(QubitId, QubitId)]) -> &mut Self {
let targets: QubitBuf = pairs.iter().map(|&(_, t)| t).collect();
self.szdg(&targets)
.h(&targets)
.crz(theta, pairs)
.h(&targets)
.sz(&targets)
}

/// Applies a general 2-qubit unitary via KAK decomposition:
/// U = (U3(before[0]) x U3(before[1])) * RXXRYYRZZ(interaction) * (U3(after[0]) x U3(after[1]))
///
Expand Down
Loading
Loading