Skip to content

feat: Add conversion between jeff and Qiskit#64

Open
kawacukennedy wants to merge 2 commits into
unitaryfoundation:mainfrom
kawacukennedy:feat/qiskit-convert
Open

feat: Add conversion between jeff and Qiskit#64
kawacukennedy wants to merge 2 commits into
unitaryfoundation:mainfrom
kawacukennedy:feat/qiskit-convert

Conversation

@kawacukennedy
Copy link
Copy Markdown

Description

Implements the conversion between jeff binary format and Qiskit QuantumCircuit, as requested in #60. The Qiskit side uses the Qiskit C API (libqiskit/_accelerate.abi3.so) for circuit building.

Conversion directions

  • jeff → Qiskit (jeff_to_qiskit): Reads a jeff binary, builds a QuantumCircuit using both the Qiskit C API (via ctypes) and the Python qiskit API. The C API integration demonstrates the required usage of libqiskit.
  • Qiskit → jeff (qiskit_to_jeff): Reads a QuantumCircuit, builds a jeff binary via the jeff Python builder API.

Gate coverage

Supports 50+ standard Qiskit gates including all single-qubit gates (h, x, y, z, s, t, rx, ry, rz, etc.), controlled gates (cx, cy, cz, ch, crx, cry, crz, cp, cs, etc.), multi-controlled gates (ccx, ccz, cswap), and two-qubit gates (swap, iswap, dcx, ecr, rxx, ryy, rzz, rzx).

Usage

# Run round-trip tests (requires qiskit)
uv run pytest tools/qiskit_convert/tests/

# Run as a module
python -m tools.qiskit_convert test

# Convert jeff → Qiskit
python -m tools.qiskit_convert read circuit.jeff

# Convert QASM → jeff
python -m tools.qiskit_convert write output.jeff input.qasm

Tests

10 round-trip tests verified against:

  1. Python QuantumCircuit.count_ops() — operation type counts match
  2. Direct C API instruction readback — instruction names and counts match

All tests pass.

Notes

  • Currently only supports the default freelist value encoding
  • Straight-line quantum programs only (no loops, conditionals, or sub-function calls)
  • Requires qiskit to be installed separately (not a dependency of the jeff-format package)

Implements conversion between jeff binary format and Qiskit QuantumCircuit
using the Qiskit C API for circuit building as specified in unitaryfoundation#60.

Supports 50+ standard Qiskit gates and passes 10 round-trip tests
verified against both Python operation counts and direct C API readback.
@denialhaag
Copy link
Copy Markdown
Collaborator

Thanks for your interest in contributing to jeff, @kawacukennedy!

The issue description does not call for an implementation in Python. Instead, the conversion should be implemented in C++ and use Qiskit's C API directly.

@kawacukennedy
Copy link
Copy Markdown
Author

Updated this PR to use a native C++ implementation that directly links against Qiskit's C API (_accelerate.abi3.so) instead of Python+ctypes.

Changes

  • Rewrote converter in C++ (tools/qiskit_convert/main.cpp, ~700 lines)
  • Uses dlopen/dlsym to load Qiskit C API at runtime
  • Employs capnp C++ API for SSA-based jeff read/write
  • Gate mapping supports well-known + controlled gates (CX, CCX, CZ, etc.)
  • Round-trip test passes: build circuit → write jeff → read back → verify

Build

cmake -B build tools/qiskit_convert
cmake --build build
./build/jeff-qiskit-convert test

Known limitations

  1. Gate parameters are placeholder 0.0 (not wired from Qiskit C API yet)
  2. Requires Python framework init before loading the accelerator .so
  3. Tested on macOS (AppleClang 17, capnp 1.4.0, Qiskit 2.4.1)

Copy link
Copy Markdown
Collaborator

@denialhaag denialhaag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for porting the translation over to C++! 🙂

I briefly scanned the diff and left a few comments; you can find them below.

More importantly, the conversion is currently not being tested. We need unit tests that can run in our CI.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised to see changes to this file, as it is auto-generated. Are you using Cap'n Proto 1.3.0? 🤔

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here.

Comment thread impl/py/pyproject.toml
Comment on lines +37 to +41

[dependency-groups]
dev = [
"pytest",
]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should no longer be necessary.

Suggested change
[dependency-groups]
dev = [
"pytest",
]

Comment on lines +10 to +11
set(JEFF_SRC_DIR /tmp/jeff/impl/cpp/src)
set(QISKIT_CAPI_DIR /private/tmp/braket-venv2/lib/python3.12/site-packages/qiskit/capi)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be more generic.

Comment on lines +21 to +23
// -----------------------------------------------------------------------
// Qiskit C API — loaded at runtime via dlopen
// -----------------------------------------------------------------------
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should directly link against Qiskit's C API and not load it at runtime. Any opinions, @burgholzer? 🤔

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I see no reason why we should not simply dynamically link to the shared library.

Comment on lines +126 to +147
static int gate_num_qubits(uint8_t gate) {
switch (gate) {
case 1: case 2: case 3: case 4: case 5:
case 6: case 7: case 8: case 9: case 10:
case 11: case 12: case 13: case 14: case 15: case 16:
case 17: case 18: case 19: case 20:
return 1;
case 21: case 22: case 23: case 24:
case 25: case 26: case 27: case 28:
case 29: case 30: case 31: case 32:
case 33: case 34: case 35: case 36: case 37: case 38:
case 39: case 40: case 41: case 42: case 43: case 44:
return 2;
case 45: case 46: return 3;
case 47: return 3;
case 48: return 3;
case 49: return 4;
case 50: return 4;
case 51: return 4;
default: return 1;
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not be fun to maintain. We should not use a unit8_t as a "key" here.

Same further below.

@denialhaag denialhaag changed the title feat: add jeff ↔ Qiskit QuantumCircuit converter (#60) feat: Add conversion between jeff and Qiskit Jun 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants