Skip to content

feat: Add initial jeff-qiskitc converter tool - #84

Open
aks8134 wants to merge 5 commits into
unitaryfoundation:mainfrom
aks8134:jeff_qiskitc
Open

feat: Add initial jeff-qiskitc converter tool#84
aks8134 wants to merge 5 commits into
unitaryfoundation:mainfrom
aks8134:jeff_qiskitc

Conversation

@aks8134

@aks8134 aks8134 commented Jul 22, 2026

Copy link
Copy Markdown

Summary

Adds tools/jeff-qiskitc, a C++ tool that converts a jeff Module into a
Qiskit QkCircuit via the Qiskit C API. jeff -> Qiskit direction only, for
now. I'll add Qiskit -> jeff direction commit, after getting feedback from reviewers regarding
the design chosen here.

  • WellKnownGate / PauliProductRotationGate / QubitGate (gate_converter.*)
    map jeff's QubitGate (wellKnown + controlled variants, and Pauli product
    rotations) onto the matching QkGate/QkPauliProductRotation.

  • Op / QubitOp / FloatOp / AllocOp / MeasureNdOp / GateOp
    (circuit_converter.*); each capnp union kind modeled as its own type

  • jeff_qiskitc.cpp is the entry point: a resource-counting pass sizes the
    QkCircuit up front, then a second pass builds it.

Currently covers QubitOp's alloc/measureNd/gate variants and Op's
qubit/float variants.

Testing

Added tools/jeff-qiskitc/tests/gate_conversion_test.cpp, exercising every
entry in WellKnownToQkGateMap/ControlledQkGateMap plus a handful of
Pauli product rotations, wired up via a new CMakeLists.txt
(ctest-registered). Locally: 216 assertions, 0 failures.

Open question for maintainers

This repo has no existing C++ build/CI setup. The new CMakeLists.txt needs QISKIT_C_DIR pointed at a
Qiskit C API install (headers + libqiskit.so), and I don't have visibility
into how CI should obtain that; build from source, a prebuilt release
artifact, or something else? Would appreciate guidance before wiring this
into CI.

Test plan

cmake -S tools/jeff-qiskitc -B build -DQISKIT_C_DIR=<path>
cmake --build build
ctest --test-dir build

AI Disclosures

All the library code and the design decisions have been made by me. For the tests and cmake files, I used claude code.

@aks8134

aks8134 commented Jul 30, 2026

Copy link
Copy Markdown
Author

Add QiskitC to Jeff converter for straight-line quantum programs

Adds the QkCircuit -> jeff direction (qiskitc_to_jeff), mirroring the
existing jeff -> QkCircuit direction added earlier.

  • tests: extends gate_conversion_test.cpp with reverse-direction cases
    for every wellKnown/controlled gate and PPR (both directions, single
    instruction at a time); adds circuit_conversion_test.cpp for
    round-trip tests (Bell pair, GHZ(10), QFT(5)) through both
    directions on realistic multi-instruction circuits.

@aks8134
aks8134 marked this pull request as ready for review July 30, 2026 09:18
@denialhaag
denialhaag self-requested a review July 30, 2026 09:29

@denialhaag denialhaag left a comment

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.

Thanks a lot for your interest in contributing to jeff, @aks8134! 🙂 I'm sorry for getting back to you only now.

This is already a nice starting point for the Qiskit conversions! I think we'll still have to iron out some stylistic aspects, but this should all be doable. As you already pointed out yourself, we should start by adding a GitHub Actions workflow that runs your new tests. You can find some thoughts on this in the comments below, together with some additional points I stumbled across while going through your PR.

Please let me know if setting up the workflow causes issues; we can help with that.

#include "capnp/jeff.capnp.h"


QkCircuit* jeff_to_qiskitc(jeff::Module::Reader module);

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.

It would be nice to add a wrapper that accepts a .jeff file. For reference, we have these functions for translating between jeff and MLIR:

Comment thread tools/jeff-qiskitc/lib/circuit_converter.cpp
Comment thread tools/jeff-qiskitc/lib/jeff_qiskitc.cpp Outdated
}

capnp::MallocMessageBuilder message;
jeff::Module::Builder module = message.initRoot<jeff::Module>();

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.

module is rendered as a built-in on GitHub, so something like mod would be better.

Suggested change
jeff::Module::Builder module = message.initRoot<jeff::Module>();
jeff::Module::Builder mod = message.initRoot<jeff::Module>();

Comment thread tools/jeff-qiskitc/lib/jeff_qiskitc.cpp Outdated
Comment on lines +52 to +56
module.setVersion(0);
module.setVersionMinor(3);
module.setVersionPatch(0);
module.setEntrypoint(0);
module.initStrings(1).set(0, "from_qkcircuit");

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.

It might also make sense to set the tool and its version here.

Comment thread tools/jeff-qiskitc/CMakeLists.txt Outdated
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(CapnProto CONFIG REQUIRED)

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.

In jeff-mlir, we fetch Cap'n Proto via FetchContent. Might also be interesting here.

Comment thread tools/jeff-qiskitc/CMakeLists.txt
@aks8134

aks8134 commented Aug 13, 2026

Copy link
Copy Markdown
Author

Thanks @denialhaag for the detailed review. I plan to make the recommended changes in the following two commits :

  1. For the suggested code changes and add cmake- / clang-format files
  2. Setting up github action workflow for running the tests

I will try to get back as soon as possible.

Ubuntu added 2 commits August 21, 2026 07:38
… I/O wrappers

- Derive jeff format version and tool name/version from generated CMake/schema constants instead of hand-typed literals
- Fix jeff_to_qiskitc to respect Module.entrypoint instead of assuming function 0
- Add jeff_file_to_qiskitc/qiskitc_to_jeff_file for reading/writing .jeff files directly, plus a test
- Add clang-format/clang-tidy/cmake-format configs matching jeff-mlir's conventions
Builds Qiskit's C API from source (pinned to the 2.5.2 tag) and runs
jeff-qiskitc's test suite on ubuntu-24.04, gated behind a paths-filter
so it only runs when jeff-qiskitc or the schema changes.
@aks8134

aks8134 commented Aug 21, 2026

Copy link
Copy Markdown
Author

Hi @denialhaag, I made the changes that u suggested. Could you let me know if anything should be changed?

@denialhaag
denialhaag self-requested a review August 23, 2026 21:08

@denialhaag denialhaag left a comment

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.

Thanks a lot for addressing all of my comments, @aks8134! 🙂 It's really cool to see the programs round-tripping successfully in the CI! 🥳

Below, you can find another set of comments that you hopefully find helpful to polish your implementation a bit. Most of them are largely stylistic; the functionality of your implementation is already at a nice point! 🙂

Comment on lines +14 to +20
QkCircuit* jeff_to_qiskitc(jeff::Module::Reader mod);

kj::Array<capnp::word> qiskitc_to_jeff(const QkCircuit* circuit);

QkCircuit* jeff_file_to_qiskitc(const std::string& path);

void qiskitc_to_jeff_file(const QkCircuit* circuit, const std::string& path);

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.

It would be nice to add Doxygen comments to these.

Comment on lines +6 to +9
exclude: |
(?x)^(
impl/cpp/.*/capnp/jeff\.capnp\.(h|c\+\+)
)$

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.

Is this necessary, given that we are in the tool directory already? 🤔


namespace {

constexpr double kPi = 3.14159265358979323846;

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.

If requiring C++20 doesn't create any problems (see one of my comments below), let's use std::numbers::pi instead.

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.

Could you try writing the tests using GoogleTest? It provides infrastructure for asserting and other nice-to-haves.

You can refer to the test configuration jeff-mlir for help, but also don't hesitate to ask us (or an LLM). 🙂

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.

If this is too much work, don't worry! We can also take care of that in a follow-up.

Comment on lines +156 to +158
jeff::Module::Reader module = reader.getRoot<jeff::Module>();

QkCircuit* roundtripped = jeff_to_qiskitc(module);

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.

Just to not trip up GitHub's syntax highlighting.

Suggested change
jeff::Module::Reader module = reader.getRoot<jeff::Module>();
QkCircuit* roundtripped = jeff_to_qiskitc(module);
jeff::Module::Reader mod = reader.getRoot<jeff::Module>();
QkCircuit* roundtripped = jeff_to_qiskitc(mod);

Comment on lines +70 to +71
enable_testing()
add_subdirectory(tests)

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.

Could you guard this similarly to how we do it in jeff-mlir? This ensures that testing is not enabled when your tool is used downstream.

uint32_t num_values = num_qubits * 2 + num_floats;
uint32_t num_ops = num_qubits + num_floats + 1;

jeff::Module::Builder module = message.initRoot<jeff::Module>();

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.

See my other comment. This also applies to other instances in this file.

Suggested change
jeff::Module::Builder module = message.initRoot<jeff::Module>();
jeff::Module::Builder mod = message.initRoot<jeff::Module>();

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.

Note to myself: Set up cpp-linter workflow afterward.

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.

Did you run the pre-commit hooks? The imports seem unsorted to me. 🤔

You can either set up prek or pre-commit and then run prek -a or pre-commit run -a, respectively.

Comment on lines +4 to +6
#define JEFF_QISKITC_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define JEFF_QISKITC_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define JEFF_QISKITC_VERSION_PATCH @PROJECT_VERSION_PATCH@

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.

Are these three used anywhere? 🤔

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.

2 participants