We work with Qiskit and it’s recently enabled a C API (code, demo) to its Rust core to provide low level access to the data model. There is some interesting potential applications with Fortran, due to the promising recent advancements in quantum computing applications to scientific computing eg. [1,2,3,4], and we’re looking for help on how to best integrate it. Bindings for other languages have been under development such as C++, Julia, and Rust and adding Fortran to this list could be really useful in certain application domains. We are looking to see if this project is of interest to folks here, from a development and application standpoint.
If anyone has any suggestions or would like to co-develop this project please feel free to comment your thoughts.
Quantum Programming is not my topic of expertise but it is something that I read about for fun and I did wonder if there would be a Fortran API out there.
So I’ll just give you my two cents because why not ![]()
If you already automated generating the C API, I think the easiest path forward would be to automate a companion twin Fortran API using iso_c_binding to expose the C procedures within a Fortran module.
A general comment to `Quantum Programming`: in the future, classical languages may need to develop language extensions to support certain rules that Quantum Physics enforce. A great example of this is the so called `no cloning theorem`. Consider an instance of a type `QUANTUM COMPLEX :: a` for which the compiler would need to enforce that the value cannot be copied. Moreover, it also cannot be read, but only once, and then be destroyed. Sure, one can think that thesecan tackeled with derived types and some assign operators. But, I think, for a healthy and userfriendly Quantum Language, especially for a high-performance computing language, these aspects must be first class citizens.
If I were a committee member, I’d start thinking about these and I think the committee should start running a task force to think if these conceps can be baked into the language. This is the future and it is coming like a zunami - nobody knows the hour, but when it comes, it will be quick. @certik @rouson
I know this is a bit besides the point, since the question was more about how to generate, transpile and measure Quantum Circuits but I think, eventually the way for a compiled high level language, the realistic and useful way forward is to be compatible with the rules imposed by quantum physics at the language level.
That is certainly something I had in mind. I’ve also been exploring a tentative architecture with ISO_C_BINDING, by looking at how established HPC libraries structure their Fortran bindings, which surfaced some of the design questions below.
Memory lifecycle: HDF5 and FFTW land on explicit manual lifecycle (h5open_f/h5close_f, fftw_plan_*/fftw_destroy_plan), and the Qiskit C API follows the same pattern with paired qk_*_new/qk_*_free per handle type. A thin ISO_C_BINDING mirror could expose those directly, OR we add a higher-level derived-type wrapper with FINAL on top, the way qiskit-cppadds RAII over the same raw C handles. The latter is more ergonomic, but I’m curious to hear from people, why manual lifecycle is otherwise preferred in certain cases? where does the distinction lie in either approach?
Memory layout: C is row-major, Fortran is column-major. A Fortran routine receiving a matrix from C will silently treat it as transposed. Do we mandate a layout convention, expose stride parameters (control to the caller), or absorb a transpose inside the wrapper (simple, but 2x memory usage)? Each of these creates a tradeoff between ergonomics, memory overhead, and where the responsibility sits (user vs. binding).
Qubit index ordering: Qiskit uses little-endian bit ordering, qubit 0 is rightmost. Quantum chemistry codes like GAMESS and NWChem export Hamiltonians in FCIDUMP format, which uses 1-based orbital indices in chemist’s notation. A mismatch here is silent and physically wrong. Worth deciding early whether the binding translates indices at the API boundary, flags a mismatch, or documents the convention and leaves it to the caller.
Once some preliminary design questions above are settled, I feel the ISO_C_BINDING layer itself is fairly mechanical to write, and the right answers to those questions live squarely within this community’s expertise.There are likely concerns I’ve overlooked too, so please feel free to bring those to light. Curious to see how this project pans out!
I would say that both approaches are valid and complementary for a robust design. If I take your reference to HDF5, you could see this as the first layer which exposes the raw functional approach but which can be hard to approach by end users. Then, a second layer of abstraction can be built on top, for instance GitHub - geospace-code/h5fortran: Lightweight HDF5 polymorphic Fortran: h5write() h5read() · GitHub this library exposes a high-level API which is easier to understand by the end users.
So, one idea could be that you first expose the C API with a module, say:
module qiskit_c_kernels
use iso_c_binding
implicit none
private
interface
subroutine qk_<*>_new_f( ..., ...) bind(c,name=qk_<*>_new)
end subroutine
...
end interface
public :: qk_<*>_new_f
end module
And then, a higher level module which uses your C exposed kernels to propose an easier to use API.
If you don’t need to expose strided subsections of matrices, I would suggest to: expose directly the matrices “as is”, document that the Fortran API is receiving the raw data so it will be viewed as transposed, and adapt the subsequent algebraic procedures to apply any required operator having this prior knowledge. Limit copies if possible.
I’m pretty sure that some might disagree with this view, but well, just an idea.
For context, since it’s relevant to the discussion:
Qiskit is essentially a quantum compiler stack. You write quantum programs in terms of abstract circuits and operators, and Qiskit handles everything between that description and actual hardware execution, optimizing and transforming circuits to match specific device constraints, and dispatching to real quantum processors through a standardized interface. The performance-critical parts of this pipeline are written in Rust, and the C API exposes those internals directly, covering the full workflow from circuit construction through compilation to execution.
Now, to clarify the scope of what we’re exploring here, all the required physics is already handled. The no-cloning theorem, measurement collapse, etc. is intrinsic to and enforced by quantum hardware itself when circuits are actually executed. The rust-native compiler optimizes and re-writes these quantum circuits to match the constraints of quantum computing hardware.
What we’re building is simply a bridge to reach those internals from Fortran, in the same way qiskit-cpp does from C++.
The architecture we have in mind is two layers:
-
A thin ISO_C_BINDING layer that maps Fortran calls directly to the C API functions (qk_circuit_new, qk_obs_*, etc.)
-
A Fortran-idiomatic module layer on top: derived types, ergonomic interfaces, Fortran array semantics, etc. to make the API feel native to someone writing HPC code
The goal is that an existing Fortran HPC code could call a quantum subroutine the same way it calls LAPACK today, without the caller ever needing to think about what’s happening in the Rust layer. That was really the intent behind starting this thread: to discuss what that API layer should look like, and to discuss, validate, and collectively shape the architecture.
Thank you for the clarification! We can drive the 2 level, FFI and API layers we had in mind, both exposed publicly to the end user, the former wrapping c functions as is (shifting the optics to not just interface data structures, but also fit into utility), and lifetime agnostic, the latter ergonomic and with explicit allocation and deallocation of resources (using final or creating and calling explicit *_free functions). But I was wondering what exactly would the caveats be with such an approach? Some I can think of:
- Perhaps there may be edge-cases which involves double
freeing of the same resource, mixing FFI (qk_*_free) + ergonomic API. Does Fortran’stype(c_ptr)provide ways to track ownership to avoid this? - If the entire purpose of language bindings is to integrate external libraries ergonomically into the target ecosystem, shall we really design Fortran bindings that require every user to manually handle row/column-major mismatches when calling standard Fortran libraries (BLAS/LAPACK/MPI)? Shouldn’t our binding layer absorb this impedance mismatch? is there perhaps a different approach to transposition? maybe we expose stride parameters, or is
copy-on-write(CoW) a viable solution, albeit eventually it’ll still take the same memory?
This has been suggested before, outside the context of quantum computing. One suggestion is to have an intrinsic shallow_transpose(A) type of operator that operates only on the array metadata to transpose the argument. This would be a pointer-like function. The cost for invoking the function would be independent of the number of elements in the array. This would of course simplify interlanguage operability (e.g. with C), but it would also be a useful feature on its own, entirely within fortran. I remember someone implemented this in gfortran just to demonstrate that the idea works. In a simple pointer assignment
B => shallow_transpose(A)
the elements of B(:,:) would point to the same memory locations as the elements of A(:,:), but with the rows and columns switched. When used as a subprogram argument
call subxxx(..., shallow_transpose(A), ...)
It would result in a modifiable actual argument where the elements of A(:,:) are modified directly, not through a temporary copy.
In principle, it could be generalized to higher rank arrays, and it could allow array slices as its argument, giving the programmer much flexibility when operating on array arguments stored in various ways.
I think the vision is great and it will be a great solution to many! Thank you for the effort!
However, I want to clarify a couple of points:
- I view Qiskit more like LLVM IR (backend) rather than a high-level full stack compiler (including the rust-native “compiler” that you mention)
- There will be, and actually there are already some, high-level compilers: Qrisp, Q# and Qmod, to name of few
- In your plan you are not addressing the issues I mention (imposing rules of quantum physics at the compiler level) and below, I will give you a couple of examples of mistakes that would not be caught at compile time
Illegal copy:
type(qbit) :: q1, q2
call init(q1)
q2 = q1 ! attempt to copy quantum state
a quantum-aware compiler would say something like:
Error: assignment of quantum object ‘q1’ to ‘q2’ is not allowed
Reason: quantum states cannot be copied (no-cloning constraint)
Use collapsed value:
type(qbit) :: q
integer :: result
call hadamard(q)
call measure(q, result)
call hadamard(q) ! using collapsed state as if still quantum
a quantum-aware compiler would say:
Error: quantum object ‘q’ used after measurement
Note: measurement consumes the quantum state
This would be analogous to using an ALLOCATABLE value after DEALLOCATE
Aliasing:
type(qbit) :: q
call init(q)
call entangle(q, q) ! same qubit used twice
QA compiler:
Error: operation ‘entangle’ requires distinct qubits
But argument 1 and 2 alias the same object
Leak:
type(qbit) :: q
call init(q)
! program ends without measurement or reset
QA compiler:
Warning: quantum object ‘q’ goes out of scope without being measured or reset
Illegal branching:
type(qbit) :: q
call hadamard(q)
if (q == 1) then ! illegal quantum state inspection
call something()
end if
QA compiler:
Error: quantum object ‘q’ cannot be used in classical condition
Hint: measure the qubit before branching
As you can see, these examples show that a quantum-aware compiler is eventually needed if one wants to use the full power of a compiler to enforce the rules of quantum computing. Hence, I think it is very important for an HPC language to have language extension for quantum computing.
Another thing is the low leveledness of Qiskit. Consider Grover
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
qc = QuantumCircuit(2, 2)
# Step 1: Superposition
qc.h([0, 1])
# Step 2: Oracle (mark |11>)
qc.cz(0, 1)
# Step 3: Diffusion operator
qc.h([0, 1])
qc.x([0, 1])
qc.cz(0, 1)
qc.x([0, 1])
qc.h([0, 1])
# Step 4: Measure
qc.measure([0, 1], [0, 1])
# Simulate
sim = AerSimulator()
result = sim.run(qc).result()
counts = result.get_counts()
print(counts)
Compare with Qrisp (huge shift in abstraction)
from qrisp import QuantumVariable, grover
# Define a 2-qubit quantum variable
qv = QuantumVariable(2)
# Define oracle as a function
def oracle(qv):
return qv == 3 # |11⟩
# Run Grover's algorithm
result = grover(qv, oracle)
print(result)
But I don’t want to discourage you, what you are doing is important and very useful for sure! I just want to increase the awareness of what is out there and what I think will be the direction where quantum languages are going. I believe Fortran has all the capacity to fulfill the requirements of quantum awareness and if it does it right (and hopefully fast), it can increase its relevance in HPC field.
Thank you for the detailed response, these are valid points and worth addressing directly.
That’s actually a fair characterisation of where Qiskit sits in the stack. It operates at the level of gates, circuits, and transpilation passes, closer to an IR than a high-level quantum language.
The examples you give of Qrisp vs raw Qiskit illustrate this well, and the higher-level compilers you mention (Qrisp, Q#, Qmod) are the right comparison for what a truly quantum-aware language would look like.
The examples you give are precise, but they assume a mental model where qubits are live quantum state objects that can be invalidated or illegally copied at runtime. Qiskit operates at a different abstraction level, a qubit in a circuit is a classical index into a circuit description, not a live quantum state. The quantum physics constraints are enforced by the hardware at execution time. The quantum-aware compiler of the kind you’re describing would sit above Qiskit in the stack, at a high-level, as you have mentioned.
What we’re building sits at the IR level, a clean compiled-language interface to Qiskit’s gate-level abstraction, which is the missing piece for the HPC community today. The language-level quantum awareness you’re describing is a genuine next objective, perhaps built around the core SDK, for applicational specificity, and arguably the more consequential conversation.
I would be extremely cautious reagarding the use of final procedures. Almost no compiler supports them reliably. They may not be called or may be called twice, in practice. I would really suggest staying away in trying to wrap memory management in Fortran OOP features: too dangerous.