Skip to content

Commit 91700af

Browse files
committed
personal compiler stuff
personal compiler stuff personal compiler stuff personal compiler stuff
1 parent b1759c7 commit 91700af

16 files changed

Lines changed: 1065 additions & 1 deletion

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ venv*/
105105
ENV/
106106
env.bak/
107107
venv.bak/
108+
.direnv/
108109

109110
# Spyder project settings
110111
.spyderproject

CLAUDE.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# CLAUDE.md — Project Instructions for Claude Code
2+
3+
## Workflow
4+
When asked about refactoring or design changes, present a 3-5 bullet proposed approach first. Do NOT edit any files until explicitly confirmed. Do not default to full rewrites — a hybrid approach preserving existing patterns may be preferred.
5+
6+
## Communication Style
7+
When asked for a recommendation, commit to a clear position. Do not flip-flop between opposing suggestions across messages. If there are genuine tradeoffs, state them once and give a final recommendation.
8+
9+
## Project
10+
MQT Core — Munich Quantum Toolkit core library.
11+
Current branch: `quaternion-rotation-merging` — refactoring the quaternion merge MLIR pass.
12+
Git layout: bare repo with worktrees (bare at `/home/anatol/git/core`, this worktree at `core/quaternion-rotation-merging`).
13+
14+
## Build & Test (justfile — user runs these, not Claude)
15+
- `just configure` — cmake + Ninja, clang, compile_commands, MLIR enabled
16+
- `just build` / `just mqt-cc` — build the compiler
17+
- `just test` — full test suite
18+
- `just test-qco` — build + run quaternion merge unit tests only
19+
- `just run-quat file=test.mlir` — run quaternion folding pass via quantum-opt
20+
- `just coverage` — gcovr HTML coverage report
21+
- Build dir: `build/Debug`, uses Ninja with `-j8`
22+
- Lint (MUST pass): `uvx nox -s lint`
23+
- LLVM 22.1+ required for MLIR
24+
25+
## Key Files
26+
- `mlir/lib/Dialect/QCO/Transforms/Optimizations/QuaternionMergeRotationGates.cpp` — main file being refactored (only file in Optimizations/)
27+
- `mlir/unittests/Dialect/QCO/Transforms/Optimizations/test_qco_quaternion_merge.cpp` — unit tests for the pass
28+
- `mlir/include/mlir/Dialect/QCO/Transforms/Passes.td` — pass tablegen definitions
29+
- `mlir/include/mlir/Dialect/QCO/Transforms/Passes.h` — pass header
30+
- `mlir/include/mlir/Dialect/QCO/IR/QCOOps.td` — QCO op definitions
31+
- `mlir/include/mlir/Dialect/QCO/IR/QCOInterfaces.td` — QCO interfaces (UnitaryOpInterface etc.)
32+
- `PLAN.md` — detailed refactoring plan (chain-based merging with global phase tracking)
33+
- `AGENTS.md` — full conventions reference (template-managed, do NOT edit)
34+
35+
## MLIR Dialect Architecture
36+
- **QC** — quantum computation (high-level gates: GPhaseOp, POp, standard gates)
37+
- **QCO** — quantum circuit optimization (IR, transforms, builder, utils)
38+
- **QIR** — quantum IR (low-level target)
39+
- **Jeff** — intermediate dialect with bidirectional QCO conversions
40+
- Conversion paths: QC↔QCO, QCO↔Jeff, QC→QIR
41+
42+
## QCO Transforms
43+
- `Optimizations/QuaternionMergeRotationGates.cpp` — quaternion merge pass
44+
- `Mapping/` — Architecture.cpp, Mapping.cpp (qubit mapping)
45+
46+
## Conventions
47+
- C++20; prefer LLVM data structures in `mlir/` (`llvm::SmallVector`, `llvm::function_ref`, etc.)
48+
- Doxygen-style comments, `#pragma once` for header guards
49+
- Always add/update tests for every code change
50+
- Commit footer: `Assisted-by: Claude Opus 4.6 via Claude Code`
51+
- Update `CHANGELOG.md` / `UPGRADING.md` for user-facing changes
52+
- Never modify template-generated files (check file header)
53+
54+
## Workflow Preferences
55+
- Prefer parallel sub-agents (Task tool) for independent work: multi-file research, concurrent build+lint, exploring separate parts of the codebase. Only sequentialize when one result feeds into the next.
56+
- Use context-mode MCP tools (`ctx_batch_execute`, `ctx_execute`, `ctx_execute_file`, `ctx_search`) for any command producing >20 lines of output. Do NOT use raw Bash for exploration or analysis.
57+
- Use `Read` only when intending to `Edit` a file afterward. For analysis, use `ctx_execute_file`.
58+
- Do not re-explore the repo structure every session — rely on this file and MEMORY.md for context.
59+
- Prefer targeted tests over full test suite during development.

SymPyQuaternions.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import math
2+
3+
from sympy import Quaternion, pi, cos, sin, N
4+
5+
6+
# ---- Gate → quaternion (SU(2) part) ----
7+
8+
9+
def r_gate(theta, phi):
10+
return Quaternion(
11+
cos(theta / 2), sin(theta / 2) * cos(phi), sin(theta / 2) * sin(phi), 0
12+
)
13+
14+
15+
def u2_gate(phi, lam):
16+
return Quaternion.from_euler([phi, pi / 2, lam], "ZYZ")
17+
18+
19+
# ---- Euler extraction matching the C++ pass exactly ----
20+
21+
22+
def normalize_angle(a):
23+
"""Normalize angle to [-pi, pi], matching the pass's normalizeAngle."""
24+
two_pi = 2 * math.pi
25+
return a - math.floor((a + math.pi) / two_pi) * two_pi
26+
27+
28+
def angles_from_quaternion(w, x, y, z):
29+
"""ZYZ Euler angles from quaternion, matching anglesFromQuaternion in the pass.
30+
31+
Does NOT normalize the quaternion sign — uses the raw Hamilton product
32+
result, just like the C++ code. Returns U gate parameters (theta, phi, lambda).
33+
"""
34+
eps = 1e-12
35+
36+
# beta = acos(clamp(2*(w^2 + z^2) - 1, -1, 1))
37+
arg = 2 * (w * w + z * z) - 1
38+
arg = max(-1.0, min(1.0, arg))
39+
beta = math.acos(arg)
40+
41+
abs_beta = abs(beta)
42+
abs_beta_minus_pi = abs(beta - math.pi)
43+
44+
safe1 = abs_beta >= eps # not near 0
45+
safe2 = abs_beta_minus_pi >= eps # not near pi
46+
safe = safe1 and safe2
47+
48+
theta_plus = math.atan2(z, w)
49+
theta_minus = math.atan2(-x, y)
50+
51+
if safe:
52+
alpha = theta_plus + theta_minus
53+
gamma = theta_plus - theta_minus
54+
elif not safe1:
55+
# beta near 0
56+
alpha = 2 * theta_plus
57+
gamma = 0.0
58+
else:
59+
# beta near pi
60+
alpha = 2 * theta_minus
61+
gamma = 0.0
62+
63+
alpha = normalize_angle(alpha)
64+
gamma = normalize_angle(gamma)
65+
66+
# U gate convention: theta=beta, phi=alpha, lambda=gamma
67+
return beta, alpha, gamma
68+
69+
70+
# ---- Global phase per gate type ----
71+
72+
73+
def global_phase(gate_type, *angles):
74+
"""Returns the global phase contribution of a gate.
75+
76+
U = e^{i*phase} * SU(2), this returns 'phase'.
77+
"""
78+
if gate_type in ("RX", "RY", "RZ", "R"):
79+
return 0
80+
elif gate_type == "P":
81+
return angles[0] / 2
82+
elif gate_type == "U":
83+
# U(theta, phi, lambda): phase = (phi + lambda) / 2
84+
theta, phi, lam = angles
85+
return (phi + lam) / 2
86+
elif gate_type == "U2":
87+
# U2(phi, lambda): phase = (phi + lambda) / 2
88+
phi, lam = angles
89+
return (phi + lam) / 2
90+
91+
92+
def output_phase(phi, lam):
93+
"""Intrinsic phase of the synthesized U(theta, phi, lambda)."""
94+
return (phi + lam) / 2
95+
96+
97+
def gphase_correction(input_phase, phi, lam):
98+
"""GPhaseOp correction = total_input_phase - output_UOp_phase."""
99+
return input_phase - output_phase(phi, lam)
100+
101+
102+
# ---- Helper to compute merge + gphase for a chain ----
103+
104+
105+
def compute_merge(chain):
106+
"""
107+
chain: list of (gate_type, quaternion, *angles)
108+
Returns (theta, phi, lambda, gphase) all as floats.
109+
110+
Uses our own Euler extraction that matches the C++ pass exactly:
111+
no quaternion sign normalization, same atan2/acos/clamp logic,
112+
same gimbal-lock handling, same angle normalization.
113+
"""
114+
_, q0, *a0 = chain[0]
115+
q = q0
116+
total_input_phase = global_phase(chain[0][0], *a0)
117+
118+
for entry in chain[1:]:
119+
gt, qi, *ai = entry
120+
q = qi.mul(q) # Hamilton product in circuit order
121+
total_input_phase += global_phase(gt, *ai)
122+
123+
# Extract Euler angles matching the pass (no sign normalization)
124+
w, x, y, z = float(N(q.a)), float(N(q.b)), float(N(q.c)), float(N(q.d))
125+
theta, phi, lam = angles_from_quaternion(w, x, y, z)
126+
127+
corr = gphase_correction(total_input_phase, phi, lam)
128+
129+
return theta, phi, lam, float(N(corr))
130+
131+
132+
# ---- Build gates ----
133+
134+
rx = Quaternion.from_euler([1, 0, 0], "xyz")
135+
ry = Quaternion.from_euler([0, 1, 0], "xyz")
136+
rz = Quaternion.from_euler([0, 0, 1], "xyz")
137+
mx = Quaternion.from_euler([-1, 0, 0], "xyz")
138+
my = Quaternion.from_euler([0, -1, 0], "xyz")
139+
mz = Quaternion.from_euler([0, 0, -1], "xyz")
140+
px = Quaternion.from_euler([pi, 0, 0], "xyz")
141+
py = Quaternion.from_euler([0, pi, 0], "xyz")
142+
pz = Quaternion.from_euler([0, 0, pi], "xyz")
143+
smallx = Quaternion.from_euler([0.001, 0, 0], "xyz")
144+
smally = Quaternion.from_euler([0, 0.001, 0], "xyz")
145+
146+
# P gate has same SU(2) quaternion as RZ
147+
p1 = Quaternion.from_euler([0, 0, 1], "xyz") # P(1) same rotation as RZ(1)
148+
149+
u1 = Quaternion.from_euler([2, 1, 3], "ZYZ") # U(1,2,3)
150+
u2 = Quaternion.from_euler([5, 4, 6], "ZYZ") # U(4,5,6)
151+
152+
u2_12 = u2_gate(1, 2)
153+
u2_34 = u2_gate(3, 4)
154+
155+
r12 = r_gate(1, 2)
156+
r34 = r_gate(3, 4)
157+
r11 = r_gate(1, 1)
158+
159+
cases = [
160+
("RX+RY", [("RX", rx), ("RY", ry)]),
161+
("RX+RZ", [("RX", rx), ("RZ", rz)]),
162+
("RY+RX", [("RY", ry), ("RX", rx)]),
163+
("RY+RZ", [("RY", ry), ("RZ", rz)]),
164+
("RZ+RX", [("RZ", rz), ("RX", rx)]),
165+
("RZ+RY", [("RZ", rz), ("RY", ry)]),
166+
("RX+RX", [("RX", rx), ("RX", rx)]),
167+
("RY+RY", [("RY", ry), ("RY", ry)]),
168+
("RZ+RZ", [("RZ", rz), ("RZ", rz)]),
169+
("U+U", [("U", u1, 1.0, 2.0, 3.0), ("U", u2, 4.0, 5.0, 6.0)]),
170+
("P+RX", [("P", p1, 1.0), ("RX", rx)]),
171+
("U2+U2", [("U2", u2_12, 1.0, 2.0), ("U2", u2_34, 3.0, 4.0)]),
172+
("R+R", [("R", r12, 1.0, 2.0), ("R", r34, 3.0, 4.0)]),
173+
("R+R same", [("R", r11, 1.0, 1.0), ("R", r11, 1.0, 1.0)]),
174+
("small RX+RY", [("RX", smallx), ("RY", smally)]),
175+
("RX(pi)+RY(pi)", [("RX", px), ("RY", py)]),
176+
("RZ+RY+RX pi", [("RZ", pz), ("RY", py), ("RX", px)]),
177+
("RY+RZ+RZ-+RY-", [("RY", ry), ("RZ", rz), ("RZ", mz), ("RY", my)]),
178+
]
179+
180+
if __name__ == "__main__":
181+
for name, chain in cases:
182+
theta, phi, lam, gphase = compute_merge(chain)
183+
print(f"{name}: U({theta}, {phi}, {lam}) gphase={gphase}")

cmake/ExternalDependencies.cmake

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ if(BUILD_MQT_CORE_MLIR)
4141
FetchContent_Declare(
4242
jeff-mlir
4343
GIT_REPOSITORY https://github.com/PennyLaneAI/jeff-mlir.git
44-
GIT_TAG c7e0d0340e470c9097a79a430b633b97dcd864e9)
44+
GIT_TAG c7e0d0340e470c9097a79a430b633b97dcd864e9
45+
PATCH_COMMAND
46+
${CMAKE_COMMAND} -E copy_if_different
47+
${PROJECT_SOURCE_DIR}/cmake/patches/jeff-mlir-SetupMLIR.cmake
48+
<SOURCE_DIR>/cmake/SetupMLIR.cmake)
4549
list(APPEND FETCH_PACKAGES jeff-mlir)
4650
endif()
4751

cmake/SetupMLIR.cmake

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,21 @@ set(MQT_MLIR_MIN_VERSION
1515

1616
# MLIR must be installed on the system
1717
find_package(MLIR REQUIRED CONFIG)
18+
if(NOT IS_ABSOLUTE "${MLIR_TABLEGEN_EXE}")
19+
execute_process(
20+
COMMAND which mlir-tblgen
21+
OUTPUT_VARIABLE MLIR_TABLEGEN_EXE
22+
OUTPUT_STRIP_TRAILING_WHITESPACE
23+
)
24+
set(MLIR_TABLEGEN_EXE "${MLIR_TABLEGEN_EXE}" CACHE FILEPATH "" FORCE)
25+
endif()
26+
message(STATUS "MLIR_TABLEGEN_EXE: ${MLIR_TABLEGEN_EXE}")
27+
message(STATUS "MLIR_FOUND: ${MLIR_FOUND}")
28+
message(STATUS "MLIR_VERSION: ${MLIR_VERSION}")
29+
message(STATUS "MLIR_DIR: ${MLIR_DIR}")
30+
message(STATUS "All MLIR vars: ${MLIR_INCLUDE_DIRS}")
1831
if(MLIR_VERSION VERSION_LESS MQT_MLIR_MIN_VERSION)
32+
message(STATUS "Found MLIR_VERSION: ${MLIR_VERSION}")
1933
message(FATAL_ERROR "MLIR version must be at least ${MQT_MLIR_MIN_VERSION}")
2034
endif()
2135
message(STATUS "Using MLIRConfig.cmake in: ${MLIR_DIR}")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
find_package(MLIR REQUIRED CONFIG)
2+
3+
# Workaround for nixpkgs where MLIR_TABLEGEN_EXE is set to a bare name
4+
# instead of an absolute path due to package splitting.
5+
# See: https://github.com/NixOS/nixpkgs/issues/XXXXX
6+
if(NOT IS_ABSOLUTE "${MLIR_TABLEGEN_EXE}")
7+
execute_process(
8+
COMMAND which mlir-tblgen
9+
OUTPUT_VARIABLE MLIR_TABLEGEN_EXE
10+
OUTPUT_STRIP_TRAILING_WHITESPACE
11+
)
12+
set(MLIR_TABLEGEN_EXE "${MLIR_TABLEGEN_EXE}" CACHE FILEPATH "" FORCE)
13+
endif()
14+
15+
message(STATUS "Using MLIRConfig.cmake in: ${MLIR_DIR}")
16+
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
17+
18+
list(APPEND CMAKE_MODULE_PATH "${MLIR_CMAKE_DIR}")
19+
list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
20+
21+
include(TableGen)
22+
include(AddLLVM)
23+
include(AddMLIR)
24+
25+
include_directories(SYSTEM ${LLVM_INCLUDE_DIRS} ${MLIR_INCLUDE_DIRS})
26+
include_directories(${PROJECT_SOURCE_DIR}/include)
27+
include_directories(${PROJECT_BINARY_DIR}/include)

commands.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
diff <(grep -oP "MQT_NAMED_BUILDER\(\K[^)]*" ./mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | grep -i "pow")
2+
diff <(grep -oP "MQT_NAMED_BUILDER\(\K[^)]*" ./mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | grep -i "pow") ./qc_programs.cpp.functions
3+
grep -oP 'void \K\w+(?=\(QCOProgramBuilder)' /home/anatol/git/core/pow-modifier/mlir/unittests/programs/qco_programs.h | grep pow
4+
diff <(grep -oP "MQT_NAMED_BUILDER\(\K[^)]*" ./mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | grep -i "pow") <(grep -oP 'void \K\w+(?=\(QCOProgramBuilder)' /home/anatol/git/core/pow-modifier/mlir/unittests/programs/qco_programs.h | grep -i "pow")
5+
diff <(grep -oP "MQT_NAMED_BUILDER\(\K[^)]*" ./mlir/unittests/Dialect/QCO/IR/test_qco_ir.cpp | grep -i "pow") <(grep -oP "MQT_NAMED_BUILDER\(\K[^)]*" ./mlir/unittests/Dialect/QC/IR/test_qc_ir.cpp | grep -i "pow")

compile_commands.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/Coverage/compile_commands.json

0 commit comments

Comments
 (0)